unit plotter;

{$mode objfpc}

interface

uses
  Classes, SysUtils, FileUtil, TAGraph, TASeries, TATransformations,
  TAIntervalSources, TATools, LResources, Forms, Controls, Graphics, Dialogs,
  Grids, LCLType
  , ComCtrls, StdCtrls, ExtCtrls
  , EditBtn
  , Buttons
  , Spin, upascaltz
  , TAChartUtils, TADrawUtils, TACustomSeries, TATypes, TAStyles
  , TASources
  , Types;

type

  { TPlotterForm }

  TPlotterForm = class(TForm)
    AstronomicalTwilightCheckBox: TCheckBox;
    SunDiffChartAxisTransformations: TChartAxisTransformations;
    SunriseDifferenceText: TLabel;
    SunriseDifferenceLabel: TLabel;
    SunriseDifferenceSeries: TLineSeries;
    PlotterSN: TLabel;

      SunDiffChartAxisTransformationsAutoScaleAxisTransform1: TAutoScaleAxisTransform;
    ZeroPlotCheckBox: TCheckBox;
    HideControlPanelButton: TButton;
    CivilTwilightCheckBox: TCheckBox;
    ClearAllButton: TButton;
    ClearAllButton1: TButton;
    ClearSelButton: TButton;
    ClipSunMoonCheckBox: TCheckBox;
    ColorSwatch: TShape;
    ContinuousLineCheckBox: TCheckBox;
    ControlButtonPanel: TPanel;
    ControlPanel: TPanel;
    ControlPanelButton: TButton;
    CursorModelLabel: TLabel;
    CursorPanel: TPanel;
    CursorModelPanel: TPanel;
    CursorSunMoonPanel: TPanel;
    DarknessCheckBox: TCheckBox;
    DefaultGroupBox: TGroupBox;
    DisplayGroup: TGroupBox;
    EditPositionButton: TBitBtn;
    FileSelectionModeGroupBox: TGroupBox;
    FileSelectionModeRadioGroup: TRadioGroup;
    FilesGroupBox: TGroupBox;
    FilterComboBox: TComboBox;
    FilterLabel: TLabel;
    GridCheckBox: TCheckBox;
    HelpButton: TButton;
    ReadingTextLabel: TLabel;
    hLabel: TLabel;
    dLabel: TLabel;
    wLabel: TLabel;
    mLabel: TLabel;
    MiscGroupBox: TGroupBox;
    MoonCheckBox: TCheckBox;
    MoonText: TLabel;
    MoonTextLabel: TLabel;
    CursorTempVoltPanel: TPanel;
    CursorTimeDarkPanel: TPanel;
    MultiFileStringGrid: TStringGrid;
    NauticalTwilightCheckBox: TCheckBox;
    PlotDirectoryButton: TBitBtn;
    PlotDirectoryEdit: TEdit;
    PlotFilesStringGrid: TStringGrid;
    PlotFileTimezoneLabel: TLabeledEdit;
    PlotNumberGroup: TRadioGroup;
    PlotterModelText: TLabel;
    PlotterModelType: TLabel;
    PositionEntry: TLabeledEdit;
    PositionGroupBox: TGroupBox;
    readingstextB: TLabel;
    readingstextC: TLabel;
    readingstextG: TLabel;
    readingstextR: TLabel;
    ReadingText: TLabel;
    ReadingTime: TLabel;
    RegionLabel: TLabel;
    ReplotButton: TButton;
    ResetToPlotDirectoryButton: TBitBtn;
    ReZoomButton: TBitBtn;
    SavePNGButton: TBitBtn;
    SaveSVGButton: TBitBtn;
    ScrollBox1: TScrollBox;
    SettingsGroupBox: TGroupBox;
    SettingsPanel: TPanel;
    sLabel: TLabel;
    SnowSeries: TLineSeries;
    SunCheckBox: TCheckBox;


    SunElevationChartSource: TListChartSource;
    SunText: TLabel;
    SunTextLabel: TLabel;
    SunTwilightCheckBox: TCheckBox;
    TemperatureCheckBox: TCheckBox;
    TemperatureReadingText: TLabel;
    TemperatureReadingTextLabel: TLabel;
    BlueSeries: TLineSeries;

      TemperatureChartAxisTransformationsAutoScaleAxisTransform1: TAutoScaleAxisTransform;

      MPSASChartAxisTransformationsAutoScaleAxisTransform1: TAutoScaleAxisTransform;
      MPSASChartAxisTransformationsLinearAxisTransform1: TLinearAxisTransform;

        SunMoonChartAxisTransformationsAutoScaleAxisTransform1: TAutoScaleAxisTransform;
    TemperatureChartAxisTransformations: TChartAxisTransformations;
    MPSASChartAxisTransformations: TChartAxisTransformations;
    Chart1: TChart;
    AstronmicalTwilightSeries: TLineSeries;
    SunTwilightSeries: TLineSeries;
    NauticalTwilightSeries: TLineSeries;
    ThreeDayCheckBox: TCheckBox;
    TimeOffsetGroup: TGroupBox;
    TimeOffsetSpinEdit: TSpinEdit;
    TimeRefGroup: TRadioGroup;
    TimezoneLabel: TLabel;
    TwilightGroup: TGroupBox;
    TZLocationBox: TComboBox;
    TZRegionBox: TComboBox;
    MinuteUpDown: TUpDown;
    HourUpDown: TUpDown;
    DayUpDown: TUpDown;
    WeekUpDown: TUpDown;
    VoltageChartAxisTransformations: TChartAxisTransformations;
    CivilTwilightSeries: TLineSeries;
    SunSeries: TLineSeries;
    ClearSeries: TLineSeries;
    GreenSeries: TLineSeries;
    MoonSeries: TLineSeries;
    MPSASSeries: TLineSeries;
    MPSASSeries2: TLineSeries;
    MPSASSeries3: TLineSeries;

      VoltageChartAxisTransformationsAutoScaleAxisTransform1: TAutoScaleAxisTransform;
      VoltageCheckBox: TCheckBox;
      VoltageText: TLabel;
      VoltageTextLabel: TLabel;
    WorkingPanel: TPanel;
    ChartPanel: TPanel;
    RedSeries: TLineSeries;
    ChartToolset1ZoomDragTool1: TZoomDragTool;
    ChartToolset1: TChartToolset;
    ChartToolset1DataPointCrosshairTool1: TDataPointCrosshairTool;
    ChartToolset1PanDragTool1: TPanDragTool;
    ChartToolset1ZoomMouseWheelTool1: TZoomMouseWheelTool;
    DateTimeIntervalChartSource1: TDateTimeIntervalChartSource;
    SunMoonChartAxisTransformations: TChartAxisTransformations;
    procedure AstronomicalTwilightCheckBoxEditingDone(Sender: TObject);
    procedure DayUpDownClick(Sender: TObject; Button: TUDBtnType);
    procedure HideControlPanelButtonClick(Sender: TObject);
    procedure ControlPanelButtonClick(Sender: TObject);
    procedure HelpButtonClick(Sender: TObject);
    procedure ChartToolset1PanDragTool1AfterMouseUp(ATool: TChartTool;
      APoint: TPoint);
    procedure HourUpDownClick(Sender: TObject; Button: TUDBtnType);
    procedure MinuteUpDownClick(Sender: TObject; Button: TUDBtnType);
    procedure TimeOffsetSpinEditChange(Sender: TObject);
    procedure TwilightSeriesGetPointerStyle(ASender: TChartSeries;
      AValueIndex: Integer; var AStyle: TSeriesPointerStyle);
    procedure ClipSunMoonCheckBoxEditingDone(Sender: TObject);
    procedure CivilTwilightCheckBoxEditingDone(Sender: TObject);
    procedure ContinuousLineCheckBoxEditingDone(Sender: TObject);
    procedure DarknessCheckBoxEditingDone(Sender: TObject);
    procedure NauticalTwilightCheckBoxEditingDone(Sender: TObject);
    procedure SavePNGButtonClick(Sender: TObject);
    procedure SaveSVGButtonClick(Sender: TObject);
    procedure FileSelectionModeRadioGroupClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure GridCheckBoxEditingDone(Sender: TObject);
    procedure MoonCheckBoxEditingDone(Sender: TObject);
    procedure MultiFileStringGridButtonClick(Sender: TObject; aCol,
      aRow: Integer);
    procedure FormResize(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure MPSASSeriesGetPointerStyle(ASender: TChartSeries;
      AValueIndex: Integer; var AStyle: TSeriesPointerStyle);
    procedure PlotDirectoryEditEditingDone(Sender: TObject);
    procedure ReZoomButtonClick(Sender: TObject);
    procedure ClearSelButtonClick(Sender: TObject);
    procedure FilterComboBoxChange(Sender: TObject);
    procedure PlotDirectoryButtonClick(Sender: TObject);
    procedure ReplotButtonClick(Sender: TObject);
    procedure ResetToPlotDirectoryButtonClick(Sender: TObject);
    procedure ChartToolset1DataPointCrosshairTool1Draw(
      ASender: TDataPointDrawTool);
    procedure EditPositionButtonClick(Sender: TObject);
    //procedure ChartButtonClick(Sender: TObject);
    procedure ClearAllButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    //procedure SettingsButtonClick(Sender: TObject);
    procedure PlotFilesStringGridClick(Sender: TObject);
    procedure PlotFilesStringGridCompareCells(Sender: TObject; ACol, ARow, BCol,
      BRow: Integer; var Result: integer);
    procedure PlotFilesStringGridHeaderClick(Sender: TObject; IsColumn: Boolean;
      Index: Integer);
    procedure SunCheckBoxEditingDone(Sender: TObject);
    procedure SunTwilightCheckBoxEditingDone(Sender: TObject);
    procedure TemperatureCheckBoxEditingDone(Sender: TObject);
    procedure TimeRefGroupClick(Sender: TObject);
    //procedure VerticalSplitterMoved(Sender: TObject);
    //procedure ThreeDayCheckBoxClick(Sender: TObject); {!!! maybe keep this !!!}
    procedure TZLocationBoxChange(Sender: TObject);
    procedure TZRegionBoxChange(Sender: TObject);
    procedure VoltageCheckBoxEditingDone(Sender: TObject);
    procedure WeekUpDownClick(Sender: TObject; Button: TUDBtnType);
    procedure ZeroPlotCheckBoxEditingDone(Sender: TObject);
  private
    { private declarations }
    procedure ReadINI();
    procedure GetCustomizations();
    procedure PlotFile(PlotFileName: String);
    procedure AddFilesToList(FilePathName : String);
    procedure FileSelect();
    Procedure FillTimezones();
    procedure SaveChart(FileType:String);
    procedure TemperatureSeriesGetPointerStyle(ASender: TChartSeries;
      AValueIndex: Integer; var AStyle: TSeriesPointerStyle);
    procedure VoltSeriesGetPointerStyle(ASender: TChartSeries; AValueIndex: Integer; var AStyle: TSeriesPointerStyle);
  public
    { public declarations }

  end;

  { Declarations outside the form }
  function FileSizeFormat(bytes:Double):string;
  function AddBackSlash(Instring:String):String;
  //function RemoveMultiSlash(Input: String): String;
  function DialogCentered(const aCaption: String;
                    const aMsg: string;
                    DlgType: TMsgDlgType;
                    Buttons: TMsgDlgButtons;
                    HelpCtx: longint;
                    Form: TForm
                    ): TModalResult;

  const
  DefaultSettingsWidth = 475;
  DefaultSettingsheight = 210;

  TwilightMax = 90;
  TwilightMin = -90;

var
  PlotterForm: TPlotterForm;

  MyLatitude: extended = 0.0; //Latitude
  MyLongitude: extended = 0.0; //Longitude
  MyElevation: extended = 0.0; //Elevation

  PlotterINIsection: String;
  AZones: TStringList; //To hold timezone names for display.
  ptzAll :TPascalTZ; // All regions for conversions.
  ptzRegion :TPascalTZ; //One region for user selection of timezones.
  subfix: ansistring; //Used for time zone conversions

  CurrentDirectory: String;
  PlotterModel:Integer;
  PlotType:String=''; //options: Color, empty for standard.

  LastRedDarkness: Real = 0.0;
  LastGreenDarkness: Real = 0.0;
  LastBlueDarkness: Real = 0.0;
  LastClearDarkness: Real = 0.0;

  FullChart:Boolean = False;

  NulPointerStyle:TSeriesPointerStyle;

  SplitterPosLeft: Integer;
  PlotterTZRegion, PlotterTZLocation:String; //Only used for plotter
  DefaultTZLocation: String;
  MeterTZRegion, MeterTZLocation:String; //Selected meter location for comparison to plotter location
  SelectedSerialNumberString:String; //Meter serial number pulled from .dat file
  TZChanging:Boolean=False;//Indicates that programmatic changes are taking place to the Time Zone

  VoltSeries: TLineSeries;
  TemperatureSeries: TLineSeries;
  MultiChart : array[0..9] of TLineSeries;

  GridVisible:Boolean;
  SunTwilightVisible, CivilTwilightVisible, NauticalTwilightVisible, AstronomicalTwilightVisible :Boolean;
  DarknessVisible:Boolean;
  VoltageVisible:Boolean;
  TemperatureVisible:Boolean;
  SunVisible:Boolean;
  MoonVisible:Boolean;
  SunriseDiffVisible:Boolean;
  TimeReference: String; {UTC or Local setting.}
  PositionUsable: Boolean;
  ClipSunMoon: Boolean; {Clip plotting of Sun/Moon below 18°.}
  ContinuousLine: Boolean; {Continuous plot line without timed separations.}
  ZeroPlot: Boolean; {Plot the lines when readings are zero.}

  CursorTimeFormat: TFormatSettings; //Cursor time format settings
  FileSelectionMode: String;

  MultiIndex:Integer; //Index to open place in multiple file grid row

  SelectedFilename: String; //The filename used on the plottor form title bar

  RecordType: Integer = 0; //0=Initial or 1=subsequent

  MinLocalRecordTime : TDateTime = 0;
  MaxLocalRecordTime : TDateTime = 0;
  MaxLocalRecordDate : TDateTime;
  MinPlotTime, MaxPlotTime: TDateTime;

  TimeOffset: Int64; //Time offset for plotting

implementation
uses
  appsettings
  , dateutils //Required to convert logged UTC string to TDateTime
  , strutils //Required for checking lines in conversion file.
  , moon //required for Moon calculations
  , Math //for float identifier
  , dlheader //For Timezone conversions.
  , LazFileUtils //Necessary for filename extraction
  , worldmap
  , header_utils //for statusmessage
  , Unit1
  , TADrawerSVG // for saving SVG of chart
  , FPCanvas //needed for pen styles
  , uPascalTZ_Types //For some time zone constants
  ;

var
    UpdatingGUI:Boolean = False;

{ TPlotterForm }

procedure TPlotterForm.MPSASSeriesGetPointerStyle(ASender: TChartSeries;
  AValueIndex: Integer; var AStyle: TSeriesPointerStyle);
var
  symbol: Double;
begin
  { White color pointers indicate an initial reading.
    Other color pointers indicate a subsequent reading.
    SVG saving is much smaller with no subsequent pointer (psNone), and the plot looks cleaner.}
 symbol := MPSASSeries.XValues[AValueIndex, 1];

  AStyle := TSeriesPointerStyle(round(symbol));{ TODO : seems to cause a problem in win64 with strange style value. }

  //if ASender.GetColor(AValueIndex)=clWhite then
  //  AStyle:=psRectangle
  //else
  //  AStyle:=psTriangle;
end;

procedure TPlotterForm.VoltSeriesGetPointerStyle(ASender: TChartSeries; AValueIndex: Integer; var AStyle: TSeriesPointerStyle);
begin
    { White color pointers indicate an initial reading.
      Other color pointers indicate a subsequent reading.
      SVG saving is much smaller with no subsequent pointer (psNone), and the plot looks cleaner.}
    if ASender.GetColor(AValueIndex)=clWhite then
      AStyle:=psRectangle
    else
      AStyle:=psNone;
end;

procedure TPlotterForm.TemperatureSeriesGetPointerStyle(ASender: TChartSeries;
  AValueIndex: Integer; var AStyle: TSeriesPointerStyle);
begin
  { White color pointers indicate an initial reading.
    Other color pointers indicate a subsequent reading.
    SVG saving is much smaller with no subsequent pointer (psNone), and the plot looks cleaner.}
  if ASender.GetColor(AValueIndex)=clWhite then
    AStyle:=psRectangle
  else
    AStyle:=psNone;
end;

procedure TPlotterForm.TwilightSeriesGetPointerStyle(
  ASender: TChartSeries; AValueIndex: Integer; var AStyle: TSeriesPointerStyle);
begin
  if ASender.GetYValue(AValueIndex)=TwilightMax then
     AStyle:=psDownTriangle
  else
      AStyle:=psTriangle;
end;

procedure TPlotterForm.FormCreate(Sender: TObject);
var
  W,H: Integer; { Temporary width and height variables. }
begin
  UpdatingGUI:=True; //prevent change events from updating while programmatic changes are being done.

  { Do not show any space for multiple files until they are added. }
  MultiFileStringGrid.RowCount:=0;

  { Initialize required variables. }

  { Fill regions }
  TZRegionBox.Items.AddStrings(TZ_FILES_STANDARD);

  AZones:=TStringList.Create;

  ptzAll := TPascalTZ.Create();
  ptzAll.ParseDatabaseFromDirectory(appsettings.TZDirectory); //Get timezones
  ptzAll.DetectInvalidLocalTimes:=False;   //Prevent errors of "time does not exist in"

  ptzRegion := TPascalTZ.Create();
  ptzRegion.DetectInvalidLocalTimes:=False;   //Prevent errors of "time does not exist in"

  ReadINI();

  PlotterForm.Position:=poScreenCenter;

  { Restore size of panel. }
  { Read in stored width and height. }
  W :=StrToIntDef(vConfigurations.ReadString(PlotterINIsection,'PanelWidth'),0);
  H :=StrToIntDef(vConfigurations.ReadString(PlotterINIsection,'PanelHeight'),0);

  if ((W=0) or (H=0)) then
    { Maximize if no stored settings. }
    PlotterForm.WindowState:=wsMaximized
  else begin
   { check for oversize, then restore valid or saved sizes.}
      if (W>Screen.Width) then PlotterForm.Width :=Screen.Width  else PlotterForm.Width :=W;
      if (H>Screen.Height) then PlotterForm.Height:=Screen.Height else PlotterForm.Height:=H;
   //if (W>1000) then PlotterForm.Width :=1000 else PlotterForm.Width :=W;
   //if (H>1000) then PlotterForm.Height:=1000 else PlotterForm.Height:=H;
  end;

  { Set up chart timestamps format. }
  DateTimeIntervalChartSource1.DateTimeFormat:='hh:mm'+#13#10+'DD-mmm'+#13#10+'yyyy';


  { Get File Selection Mode setting. }
  FileSelectionMode:=vConfigurations.ReadString(PlotterINIsection,'FileSelectionMode','Single');
  case FileSelectionMode of
    'Accumulate': begin
       FileSelectionModeRadioGroup.ItemIndex:=1;
       MultiFileStringGrid.Visible:=False;
    end;
    'Multiple': begin
       FileSelectionModeRadioGroup.ItemIndex:=2;
       MultiFileStringGrid.Visible:=True;
    end;
    else begin //Default to Single mode
       FileSelectionModeRadioGroup.ItemIndex:=0;
       MultiFileStringGrid.Visible:=False;
    end;
  end;

  { Get Time reference setting. }
  TimeReference:=vConfigurations.ReadString(PlotterINIsection,'TimeReference','');
  { Change chart legend accordingly. }
  case TimeReference of
    'Local': begin
               TimeRefGroup.ItemIndex:=1;
               Chart1.AxisList[0].Title.Caption:='Sample time (Local)';
             end
    else begin
            TimeRefGroup.ItemIndex:=0;
            Chart1.AxisList[0].Title.Caption:='Sample time (UTC)';
         end;
  end;

  { Get chart grid setting}
  GridVisible:=vConfigurations.ReadBool(PlotterINIsection,'Grid',True);
  GridCheckBox.Checked:=GridVisible;
  Chart1.BottomAxis.Grid.Visible:=GridVisible;
  Chart1.LeftAxis.Grid.Visible:=GridVisible;

  {Get twilight lines visibility}
  SunTwilightVisible:=vConfigurations.ReadBool(PlotterINIsection,'SunTwilight',True);
  CivilTwilightVisible:=vConfigurations.ReadBool(PlotterINIsection,'CivilTwilight',True);
  NauticalTwilightVisible:=vConfigurations.ReadBool(PlotterINIsection,'NauticalTwilight',True);
  AstronomicalTwilightVisible:=vConfigurations.ReadBool(PlotterINIsection,'AstronomicalTwilight',True);

  {Set check boxes}
  SunTwilightCheckBox.Checked:=SunTwilightVisible;
  CivilTwilightCheckBox.Checked:=CivilTwilightVisible;
  NauticalTwilightCheckBox.Checked:=CivilTwilightVisible;
  AstronomicalTwilightCheckBox.Checked:=AstronomicalTwilightVisible;

  { Set plotter options}
  if SunTwilightVisible then
    SunTwilightSeries.LinePen.Style:=psSolid
  else
    SunTwilightSeries.LinePen.Style:=psClear;

  if CivilTwilightVisible then
    CivilTwilightSeries.LinePen.Style:=psSolid
  else
    CivilTwilightSeries.LinePen.Style:=psClear;

  if NauticalTwilightVisible then
    NauticalTwilightSeries.LinePen.Style:=psSolid
  else
    NauticalTwilightSeries.LinePen.Style:=psClear;

  if AstronomicalTwilightVisible then
    AstronmicalTwilightSeries.LinePen.Style:=psSolid
  else
    AstronmicalTwilightSeries.LinePen.Style:=psClear;
  {Finished getting twilight lines visibility}

  {Get setting to clip Sun/Moon plots below 18°}
  ClipSunMoon:=vConfigurations.ReadBool(PlotterINIsection,'ClipSunMoon',False);
  ClipSunMoonCheckBox.Checked:=ClipSunMoon;

  {Get setting to clip Sun/Moon plots below 18°}
  ContinuousLine:=vConfigurations.ReadBool(PlotterINIsection,'ContinuousLine',False);
  ContinuousLineCheckBox.Checked:=ContinuousLine;

  {Get setting for plotting to zero readings.}
  ZeroPlot:=vConfigurations.ReadBool(PlotterINIsection,'ZeroPlot',False);
  ZeroPlotCheckBox.Checked:=ZeroPlot;


  { Get Darkness visibility setting}
  DarknessVisible:=vConfigurations.ReadBool(PlotterINIsection,'DarknessVisible',True);
  DarknessCheckBox.Checked:=DarknessVisible;
  ReadingText.Visible:=DarknessVisible;
  ReadingTextLabel.Visible:=DarknessVisible;
  if DarknessVisible then begin
    MPSASSeries.LinePen.Style:=psSolid;
    MPSASSeries2.LinePen.Style:=psSolid;
    MPSASSeries3.LinePen.Style:=psSolid;
  end
  else begin
    MPSASSeries.LinePen.Style:=psClear;
    MPSASSeries2.LinePen.Style:=psClear;
    MPSASSeries3.LinePen.Style:=psClear;
  end;

  { Allow for extra data like pointerstyle in each record}
  MPSASSeries.ListSource.XCount:=2;

  { Get Sun visibility setting}
  SunVisible:=vConfigurations.ReadBool(PlotterINIsection,'Sun',True);
  SunCheckBox.Checked:=SunVisible;
  if SunVisible then
    SunSeries.LinePen.Style:=psSolid
  else
    SunSeries.LinePen.Style:=psClear;
  SunText.Visible:=SunVisible;
  SunTextLabel.Visible:=SunVisible;

  { Get Moon visibility setting}
  MoonVisible:=vConfigurations.ReadBool(PlotterINIsection,'Moon',True);
  MoonCheckBox.Checked:=MoonVisible;
  if MoonVisible then
    MoonSeries.LinePen.Style:=psSolid
  else
    MoonSeries.LinePen.Style:=psClear;
  MoonText.Visible:=MoonVisible;
  MoonTextLabel.Visible:=MoonVisible;

  Chart1.AxisList[2].Visible:= (SunVisible or MoonVisible);

  { Add Axis }

  {Add fixed series here. }
  { Voltage for DL records. }
  VoltSeries := TLineSeries.Create(self);
  VoltSeries.SeriesColor:=clBlue;
  VoltSeries.LinePen.Width:=2;
  VoltSeries.Title:='Voltage';
  VoltSeries.AxisIndexY:=3; //Voltage axis
  VoltSeries.Pointer.HorizSize:=2;
  VoltSeries.Pointer.VertSize:=2;
  VoltSeries.Pointer.Pen.Color:=clBlue;
  VoltSeries.Pointer.Visible:=True;
  VoltSeries.OnGetPointerStyle:=@VoltSeriesGetPointerStyle;
  Chart1.AddSeries(VoltSeries);

  { Get chart voltage visibilitysetting}
  VoltageVisible:=vConfigurations.ReadBool(PlotterINIsection,'Voltage',True);
  VoltageCheckBox.Checked:=VoltageVisible;
  if VoltageVisible then begin
    VoltSeries.LinePen.Style:=psSolid;
    Chart1.AxisList[3].Visible:=True;
  end
  else begin
    VoltSeries.LinePen.Style:=psClear;
    Chart1.AxisList[3].Visible:=False;
  end;
  VoltageText.Visible:=VoltageVisible;
  VoltageTextLabel.Visible:=VoltageVisible;

  { Temperature series. }
  TemperatureSeries := TLineSeries.Create(self);
  TemperatureSeries.SeriesColor:=$00008000;//was clLime until 20190707;
  TemperatureSeries.LinePen.Width:=2;
  TemperatureSeries.Title:='Temperature';
  TemperatureSeries.AxisIndexY:=4; //Temperature axis
  TemperatureSeries.Pointer.HorizSize:=2;
  TemperatureSeries.Pointer.VertSize:=2;
  TemperatureSeries.Pointer.Pen.Color:=$00008000;
  TemperatureSeries.Pointer.Visible:=True;
  TemperatureSeries.OnGetPointerStyle:=@TemperatureSeriesGetPointerStyle;
  Chart1.AddSeries(TemperatureSeries);
  Chart1.AxisList[4].Marks.LabelFont.Color:=TemperatureSeries.SeriesColor;
  Chart1.AxisList[4].Title.LabelFont.Color:=TemperatureSeries.SeriesColor;

  { Get chart Temperature visibilitysetting}
  TemperatureVisible:=vConfigurations.ReadBool(PlotterINIsection,'Temperature',True);
  TemperatureCheckBox.Checked:=TemperatureVisible;
  if TemperatureVisible then begin
    TemperatureSeries.LinePen.Style:=psSolid;
    Chart1.AxisList[4].Visible:=True;
  end
  else begin
    TemperatureSeries.LinePen.Style:=psClear;
    Chart1.AxisList[4].Visible:=False;
  end;

  TemperatureReadingText.Visible:=TemperatureVisible;
  TemperatureReadingTextLabel.Visible:=TemperatureVisible;


  UpdatingGUI:=False; //Allow change events to update

  { Update chart }
  Chart1.Invalidate;

end;

//procedure TPlotterForm.SettingsButtonClick(Sender: TObject);
//begin
//  if (FullChart) then begin
//    OptionsPanel.Visible:=True;
//    VerticalSplitter.Left:=SplitterPosLeft;
//    ChartButton.Down:=False;
//    FullChart:=False;
//  end;
//
//  SettingsButton.down:=True;
//
//end;

//procedure TPlotterForm.ChartButtonClick(Sender: TObject);
//begin
//  if (not FullChart) then begin
//    OptionsPanel.Visible:=False;
//    {Save splitter position}
//    SplitterPosLeft:=VerticalSplitter.Left;
//    VerticalSplitter.Left:=0;
//    SettingsButton.down:=False;
//    FullChart:=True;
//  end;
//
//  ChartButton.Down:=True;
//
//end;

procedure TPlotterForm.EditPositionButtonClick(Sender: TObject);
begin
  worldmap.FormWorldmap.WorldmapShow('Plotter', PositionEntry.Text );
end;

{Clear plot
  - All series must be cleared to clear bottom axis range.}
procedure TPlotterForm.ClearAllButtonClick(Sender: TObject);
var
  i: Integer = 0;
begin
 MPSASSeries.Clear;
 VoltSeries.Clear;
 TemperatureSeries.Clear;
 MPSASSeries2.Clear;
 MPSASSeries3.Clear;
 RedSeries.Clear;
 GreenSeries.Clear;
 BlueSeries.Clear;
 ClearSeries.Clear;
 SunSeries.Clear;
 MoonSeries.Clear;
 SunTwilightSeries.Clear;
 CivilTwilightSeries.Clear;
 AstronmicalTwilightSeries.Clear;
 NauticalTwilightSeries.Clear;

 { Clear multiple plots }
 for i:=0 to sizeof(MultiChart)-1 do begin
   Chart1.DeleteSeries(MultiChart[i]);
 end;
 MultiFileStringGrid.Clear;

 MinLocalRecordTime := 0;
 MaxLocalRecordTime := 0;

end;

procedure TPlotterForm.ReZoomButtonClick(Sender: TObject);
begin
  Chart1.ZoomFull();
end;

procedure TPlotterForm.FormResize(Sender: TObject);
begin
  { Save width and height, }
  vConfigurations.WriteString(PlotterINIsection,'PanelWidth',IntToStr(PlotterForm.Width));
  vConfigurations.WriteString(PlotterINIsection,'PanelHeight',IntToStr(PlotterForm.Height));
end;

procedure TPlotterForm.FileSelectionModeRadioGroupClick(Sender: TObject);
begin
  if not UpdatingGUI then begin

   { Clear plot since new file mode has been selected. }
   ClearAllButtonClick(nil);

   case FileSelectionModeRadioGroup.ItemIndex of
     0: begin
        FileSelectionMode:='Single';
        MultiFileStringGrid.Visible:=False;
        { TODO : Note should read: Select one file ata time to plot. }
     end;
     1: begin
        FileSelectionMode:='Accumulate';
        MultiFileStringGrid.Visible:=False;
        { TODO : Note should read: Select sequential files in chronological order. }
     end;
     2: begin
        FileSelectionMode:='Multiple';
        MultiFileStringGrid.Visible:=True;
        { No note can be seen since the grid is shown in the not place. }
     end;
   end;

   { Save Time reference setting. }
   vConfigurations.WriteString(PlotterINIsection,'FileSelectionMode',FileSelectionMode);

 end;//End of updating check
end;

procedure TPlotterForm.FormDestroy(Sender: TObject);
begin
  if Assigned(AZones) then FreeAndNil(AZones);
  if Assigned(ptzAll) then FreeAndNil(ptzAll);
  if Assigned(ptzRegion) then FreeAndNil(ptzRegion);
  if Assigned(VoltSeries) then FreeAndNil(VoltSeries);
  if Assigned(TemperatureSeries) then FreeAndNil(TemperatureSeries);
end;

procedure TPlotterForm.GridCheckBoxEditingDone(Sender: TObject);
begin
  GridVisible:=GridCheckBox.Checked;
  {Save setting for future}
  vConfigurations.WriteBool(PlotterINIsection,'Grid',GridVisible);

  Chart1.BottomAxis.Grid.Visible:=GridVisible;
  Chart1.LeftAxis.Grid.Visible:=GridVisible;
  Chart1.Invalidate;
end;

procedure TPlotterForm.MoonCheckBoxEditingDone(Sender: TObject);
begin
  if not UpdatingGUI then begin
    MoonVisible:=MoonCheckBox.Checked;
    {Save setting for future}
    vConfigurations.WriteBool(PlotterINIsection,'Moon',MoonVisible);

    if MoonVisible then begin
      MoonSeries.LinePen.Style:=psSolid;
    end else begin
      MoonSeries.LinePen.Style:=psClear;
    end;

   MoonText.Visible:=MoonVisible;
   MoonTextLabel.Visible:=MoonVisible;

    Chart1.AxisList[2].Visible:= (SunVisible or MoonVisible);

  end;
end;

procedure TPlotterForm.MultiFileStringGridButtonClick(Sender: TObject; aCol,
  aRow: Integer);
begin
  //writeln(format('button click col=%d, row=%d',[aCol, aRow]));
  //ColorDialog1.Execute;
  //MultiFileStringGrid.Cells[0,0].;
end;

procedure TPlotterForm.FormShow(Sender: TObject);
begin
  PlotFileDirectory:=RemoveMultiSlash(PlotDirectoryEdit.Text);
  AddFilesToList(PlotFileDirectory);

end;


{User has selected or changed the directory of files to choose plotfile from}
procedure TPlotterForm.PlotDirectoryEditEditingDone(Sender: TObject);
begin
  PlotFileDirectory:=RemoveMultiSlash(PlotDirectoryEdit.Text);
  AddFilesToList(PlotFileDirectory);
end;

procedure TPlotterForm.ClearSelButtonClick(Sender: TObject);
begin
  case PlotNumberGroup.ItemIndex of
    0: MPSASSeries.Clear;
    1: MPSASSeries2.Clear;
    2: MPSASSeries3.Clear;
  end;
  SunSeries.Clear;
  MoonSeries.Clear;
  SunTwilightSeries.Clear;
  CivilTwilightSeries.Clear;
  AstronmicalTwilightSeries.Clear;
  NauticalTwilightSeries.Clear;
  VoltSeries.Clear;
  SunriseDifferenceSeries.Clear;
end;

procedure TPlotterForm.FilterComboBoxChange(Sender: TObject);
begin
  AddFilesToList(PlotFileDirectory);
end;

procedure TPlotterForm.PlotDirectoryButtonClick(Sender: TObject);
begin
  if (SelectDirectory('Select the .dat directory',PlotDirectoryEdit.Text, PlotFileDirectory)) then begin
    {Assign setting}
    PlotDirectoryEdit.Text:=PlotFileDirectory;
    {Save setting to file}
    vConfigurations.WriteString(PlotterINIsection,'TreeViewPath', RemoveMultiSlash(PlotFileDirectory));
    {List files}
    AddFilesToList(PlotFileDirectory);
  end;
end;

procedure TPlotterForm.GetCustomizations();

procedure GetColor(O:TLineSeries; Name, DefaultColor:String);
var
    Color:TColor;
begin
  Color:=StringToColor(vConfigurations.ReadString(PlotterINIsection,Name,DefaultColor));
  O.SeriesColor :=Color;
  O.Pointer.Pen.Color:=Color;
end;

procedure GetWidth(O:TLineSeries; Name:String; DefaultWidth:Integer);
var
    Width:Integer;
begin
  Width:=StrToIntDef(vConfigurations.ReadString(PlotterINIsection,Name),DefaultWidth);
  O.LinePen.Width:=Width;
end;

begin
      {Get plot line customizations
        Color names: https://wiki.lazarus.freepascal.org/Colors#Convert_TColor_to.2Ffrom_string
        Color names are not case sensitive.
      }

      GetColor(MPSASSeries, 'MPSASColor','clRed');
      GetColor(MPSASSeries2, 'MPSAS2Color','clLime');
      GetColor(MPSASSeries3, 'MPSAS3Color','clBlue');
      GetColor(MoonSeries, 'MoonColor','clBlack');
      GetColor(SunSeries,'SunColor','$000097FF');
      GetColor(CivilTwilightSeries,'CivilColor','clTeal');
      GetColor(NauticalTwilightSeries,'NauticalColor','clNavy');
      GetColor(AstronmicalTwilightSeries,'AstronmicalColor','clBlack');
      GetColor(SunTwilightSeries,'SunTwilightColor','clAqua');

      GetWidth(MPSASSeries,'MPSASWidth',2);
      GetWidth(MPSASSeries2,'MPSAS2Width',2);
      GetWidth(MPSASSeries3,'MPSAS3Width',2);
      GetWidth(MoonSeries,'MoonWidth',1);
      GetWidth(SunSeries,'SunWidth',1);
      GetWidth(CivilTwilightSeries,'CivilWidth',1);
      GetWidth(NauticalTwilightSeries,'NauticalWidth',1);
      GetWidth(AstronmicalTwilightSeries,'AstronmicalWidth',1);
      GetWidth(SunTwilightSeries,'SunTwilightWidth',1);

      if VoltageVisible then begin
        GetColor(VoltSeries,'VoltageColor','clBlue');
        GetWidth(VoltSeries,'VoltageWidth',2);
      end;

      if TemperatureVisible then begin
       GetColor(TemperatureSeries,'TemperatureColor','$00008000');
       GetWidth(TemperatureSeries,'TemperatureWidth',2);
      end;

end;

procedure TPlotterForm.ReplotButtonClick(Sender: TObject);
begin

  if (FileSelectionMode='Single') then
    ClearSelButtonClick(nil);
  FileSelect();
end;


{Reset plot file directory to logs file directory}
procedure TPlotterForm.ResetToPlotDirectoryButtonClick(Sender: TObject);
begin
  {Reset}
  PlotFileDirectory:= RemoveMultiSlash(appsettings.LogsDirectoryDefault());
  PlotDirectoryEdit.Text:=PlotFileDirectory;

  {Save selection}
  vConfigurations.WriteString(PlotterINIsection,'TreeViewPath',RemoveMultiSlash(PlotDirectoryEdit.Text));

  {List files}
  AddFilesToList(PlotFileDirectory);
end;

{ Display the cursor position value }
procedure TPlotterForm.ChartToolset1DataPointCrosshairTool1Draw(
  ASender: TDataPointDrawTool);
var
    RedValue, GreenValue, BlueValue:Byte;
    Red, Green, Blue, Clear, MaximumRGB, MinimumRGB, MaximumColor, MinimumColor,  Range: Float;
    DarknessValue: Float;
begin

  if FileSelectionMode<>'Multiple' then begin

  { Ensure that cursor index is within the plotter value. }
  if ((ASender.PointIndex<MPSASSeries.Count) and (ASender.PointIndex>0)) then begin

    if (PlotType='Color') then begin //color model
      if (ASender.PointIndex<RedSeries.Count) then begin
       Red:=RedSeries.GetYValue(ASender.PointIndex);
       readingstextR.Caption:= FloatToStr(Red);
       ReadingTime.Caption:=DateTimeToStr(RedSeries.GetXValue(ASender.PointIndex),CursorTimeFormat)
      end else
        readingstextR.Caption:= '';

      if (ASender.PointIndex<GreenSeries.Count) then begin
       Green:=GreenSeries.GetYValue(ASender.PointIndex);
       readingstextG.Caption:= FloatToStr(Green);
      end else
        readingstextG.Caption:= '';

      if (ASender.PointIndex<BlueSeries.Count) then begin
       Blue:=BlueSeries.GetYValue(ASender.PointIndex);
       readingstextB.Caption:= FloatToStr(Blue);
      end else
        readingstextB.Caption:= '';

      if (ASender.PointIndex<ClearSeries.Count) then begin
       Clear:=ClearSeries.GetYValue(ASender.PointIndex);
       readingstextC.Caption:= FloatToStr(Clear);
      end else
        readingstextC.Caption:= '';

      {Make swatch ...}
      { - Invert darkness value to get brightness values }
      Red:= -1 * Red;
      Green:= -1 * Green;
      Blue:= -1 * Blue;
      Clear:= -1 * Clear;
      { - Find MaximumRGB }
      MaximumRGB:=abs(Max(Max(Red,Green),Blue));
      MaximumColor:=abs(Max(MaximumRGB,Clear));
      { - Find MinimumRGB }
      MinimumRGB:=abs(Min(Min(Red,Green),Blue));
      MinimumColor:=abs(Max(MinimumRGB,Clear));
      { - Apply offset}
      Red:=Red+MinimumRGB;
      Green:=Green+MinimumRGB;
      Blue:=Blue+MinimumRGB;
      { - Normalize values }
      Range:=abs(MaximumRGB - MinimumRGB);
      RedValue:= round(Red*255/Range);
      GreenValue:=round(Green*255/Range);
      BlueValue:=round(Blue*255/Range);
      { - Colorize the swatch }
      ColorSwatch.Brush.Color:=RGBToColor(RedValue, GreenValue, BlueValue);
      {Darkness value range}
      //actual darkness values not shown here because they are unreliable since dark calibration is too low of a value.
      ReadingText.Caption:= Format('%.2f to %.2fm',[MaximumColor,MinimumColor]);
    end else begin
      {Non-color models}
      DarknessValue:=MPSASSeries.GetYValue(ASender.PointIndex);
      if IsNan(DarknessValue) then
        ReadingText.Caption:=''
      else
        ReadingText.Caption:= Format('%.2fm',[DarknessValue]);
      ReadingTime.Caption:=DateTimeToStr(MPSASSeries.GetXValue(ASender.PointIndex),CursorTimeFormat);

      if (ASender.PointIndex<VoltSeries.Count) then  {Check if within range.}
        VoltageText.Caption:= Format('%.2fV',[VoltSeries.GetYValue(ASender.PointIndex)])
      else
        VoltageText.Caption:='';
    end;

    if (ASender.PointIndex<TemperatureSeries.Count) then  {Check if within range.}
      TemperatureReadingText.Caption:= Format('%.2f°C',[TemperatureSeries.GetYValue(ASender.PointIndex)])
    else
      TemperatureReadingText.Caption:= '';


    {Sun plot cursor value}
    if (SunVisible and (ASender.PointIndex<SunSeries.Count))then
      SunText.Caption:=Format('%.1f°',[SunSeries.GetYValue(ASender.PointIndex)])
    else
      SunText.Caption:='';

    {Sunrise difference value}
    if (SunriseDiffVisible and (ASender.PointIndex<SunriseDifferenceSeries.Count))then
      SunriseDifferenceText.Caption:=Format('%.1fs',[SunriseDifferenceSeries.GetYValue(ASender.PointIndex)])
    else
      SunriseDifferenceText.Caption:='';


    {Moon plot cursor value}
    if (MoonVisible and (ASender.PointIndex<MoonSeries.Count)) then
      MoonText.Caption:=Format('%.1f°',[MoonSeries.GetYValue(ASender.PointIndex)])
    else
      MoonText.Caption:='';


   end
   else begin //the cursor points outside the plot data
       ReadingText.Caption:= '';
       ReadingTime.Caption:='';
   end;

  end;
end;


{ Populate form from INI file }
procedure TPlotterForm.ReadINI();
var
  pieces: TStringList;

begin

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

   PlotterINIsection:='Plotter:';

   { Read previously selected file path }
   PlotDirectoryEdit.Text:=RemoveMultiSlash(vConfigurations.ReadString(
                              PlotterINIsection,
                              'TreeViewPath',
                              appsettings.LogsDirectory+DirectorySeparator
                              ));

   { Pull Timezone information from INI file if it exists.}
   PlotterTZRegion:=vConfigurations.ReadString(PlotterINIsection,'Local region');
   TZRegionBox.Text:= PlotterTZRegion;
   FillTimezones();

   { Read the previously recorded entries. }
   DefaultTZLocation:= vConfigurations.ReadString(PlotterINIsection,'Local time zone');
   TZLocationBox.Text:=DefaultTZLocation;

   PositionEntry.Text:=vConfigurations.ReadString(PlotterINIsection,'Position');
   //Parse location
   pieces.DelimitedText := PositionEntry.Text;
   if pieces.Count>11 then begin
    {Check if European commas were used instead of decimal points}
    if pieces.Count>=5 then begin
     MyLatitude:=StrToFloatDef(pieces.Strings[0]+'.'+pieces.Strings[1],0,FPointSeparator);
     MyLongitude:=StrToFloatDef(pieces.Strings[2]+'.'+pieces.Strings[3],0,FPointSeparator);
    end else begin
     MyLatitude:=StrToFloatDef(pieces.Strings[0],0,FPointSeparator);
     MyLongitude:=StrToFloatDef(pieces.Strings[1],0,FPointSeparator);
    end;
   end else begin
     MyLatitude:=0;
     MyLongitude:=0;
   end;
   //Parse elevation
   if pieces.Count>2 then begin
     MyElevation:=StrToFloatDef(pieces.Strings[2],0,FPointSeparator);
   end else begin
     MyElevation:=0;
   end;

   if Assigned(pieces) then FreeAndNil(pieces);

end;
procedure TPlotterForm.PlotFile(PlotFileName: String);
var
    File1: TextFile;
    Str: String;
    pieces: TStringList;
    i: Integer = 0; //General purpose counter
    j: Integer = 0; //General purpose counter
    MSASField: Integer = -1; //Field that contains the MSAS variable, -1 = not defined  yet.
    CountsField: Integer = -1; //Field that contains the count/time variable, -1 = not defined  yet.
    ScaleField: Integer = -1; //Field that contains the Scale variable, -1 = not defined  yet.
    ColorField: Integer = -1; //Field that contains the Color variable, -1 = not defined  yet.
    TemperatureField: Integer = -1; //Field that contains the Temperature variable, -1 = not defined  yet.
    VoltageField: Integer = -1; //Field that contains the Voltage variable, -1 = not defined  yet.
    RecordTypeField: Integer = -1; //Field that contains the Record Type (Initial/subsequent) variable, -1 = not defined  yet.
    StdLinField: Integer = -1; //Field that contains the Record Type (Standard Linear value (from Snow unit)) variable, -1 = not defined  yet.
    SnowMSASField: Integer = -1; //Field that contains the Record Type (Snow reading) variable, -1 = not defined  yet.
    SnowLinField: Integer = -1; //Field that contains the Record Type (Snow Linear value (from Snow unit)) variable, -1 = not defined  yet.
    SunriseDiffField:Integer = -1; //Field that contains the Sunrise difference variable, -1 = not defined  yet.

    MoonElevation: extended = 0.0;
    MoonAzimuth: extended = 0.0;

    SunElevation: extended = 0.0;
    SunAzimuth: extended = 0.0;

    LocalRecordTime,UTCRecordTime,PlotRecordTime : TDateTime;
    LastPlotRecordTime : TDateTime = 0;
    LastRecordMinuteSpan : TDateTime = 0;
    PlotLocalRecordDate, PlotUTCRecordDate : TDateTime;
    TwilightMorningSunTimeStamp,TwilightEveningSunTimeStamp :TDateTime;
    TwilightMorningCivilTimeStamp,TwilightEveningCivilTimeStamp :TDateTime;
    TwilightMorningAstronomicalTimeStamp,TwilightEveningAstronomicalTimeStamp :TDateTime;
    TwilightMorningNauticalTimeStamp,TwilightEveningNauticalTimeStamp :TDateTime;
    Darkness: Float = 0.0;
    LastDarkness: Float = 0.0;
    Temperature: Float = 0.0;
    Voltage: Float = 0.0;
    SnowMSAS: float = 0.0;
    SunriseDifference: float = 0.0;
    FirmwareString: String;
    DateString:String;
    FS: TFormatSettings;
    SerialINIsection:String;
    Plottable:Boolean = False;//flag to indentify if plot can be made.
    datLocalTZ: String; //Timezone information stored in the file.

    //RecordLabel:String; //Pointer label
    MPSASPointerColor:TColor; //Pointer color
    VoltagePointerColor:TColor; //Pointer color
    TemperaturePointerColor:TColor; //Pointer color
    symbol: TSeriesPointerStyle;

    RequireSunMoonElevationLabel: Boolean;

procedure PositionParse();
begin
   { Get Lat, Lon for Moon calculations. }
   pieces.Delimiter := ',';

   { Separate the fields of the lat,lon.}
   pieces.DelimitedText := PositionEntry.Text;

   {Assume that position is unusable unless something below fails}
   PositionUsable:=True;

   if (pieces.Count > 1) then begin
    {Check if European commas were used instead of decimal points}
    if pieces.Count>=5 then begin
     MyLatitude:=StrToFloatDef(pieces.Strings[0]+'.'+pieces.Strings[1],0,FPointSeparator);
    end else
     MyLatitude:=StrToFloat(pieces.Strings[0],FPointSeparator);
     if ((MyLatitude<-90.0) or (MyLatitude>90.0)) then begin
         PositionUsable:=False;
         MessageDlg('Latitude Error',
           'Error: Latitude ' + FloatToStr(MyLatitude)+' out of range.',
           mtError,
           [mbOK],0);
     end;
     {Check if European commas were used instead of decimal points}
     if pieces.Count>=5 then begin
      MyLongitude:=StrToFloatDef(pieces.Strings[2]+'.'+pieces.Strings[3],0,FPointSeparator);
     end else
       MyLongitude:=StrToFloat(pieces.Strings[1],FPointSeparator);
     if ((MyLongitude<-180.0) or (MyLongitude>180.0)) then begin
         PositionUsable:=False;
         MessageDlg('Longitude Error',
           'Error: Longitude ' + FloatToStr(MyLongitude)+' out of range.',
           mtError,
           [mbOK],0);
     end;
   end else begin
      {Improper or no position is entered, position is not usable. }
      PositionUsable:=False;
   end;

end;

begin
   screen.Cursor:= crHourGlass;
   StatusMessage(format('Plotting file: %s',[PlotFileName]));
   pieces := TStringList.Create;
   AssignFile(File1, PlotFileName);

   {for .txt files probably from sqm reader by knightware.
     - cannot get this to work on some 2 digit year timestamp files.
     - need more sample files to test against.}
   FS := DefaultFormatSettings;
   FS.DateSeparator := '/';
   FS.ShortDateFormat := 'yyyy-mm-dd';
   FS.ShortTimeFormat := 'hh:nn:ss';

   CursorTimeFormat := DefaultFormatSettings;
   CursorTimeFormat.DateSeparator := '/';
   CursorTimeFormat.ShortDateFormat := 'yyyy-mm-dd';
   CursorTimeFormat.ShortTimeFormat := 'hh:nn:ss';

   { Read the region database table. }
   {  Required for converting local to GMT, local zone must be found from region selection.}
   {  Since region is not stored in .dat header, entire database of zones must be loaded}
   //if (FileExists(appsettings.TZDirectory+PlotterTZRegion) and (length(PlotterTZRegion)>0))then begin
   //  ptz.Destroy;
   //  ptz := TPascalTZ.Create();
   //  ptz.ParseDatabaseFromDirectory(appsettings.TZDirectory);
   //  Azones.Clear;
   //  ptz.GetTimeZoneNames(AZones,true); //only geo name = true
   //end;

   case ExtractFileExt(PlotFileName) of


   '.txt':begin

       if (AnsiContainsStr(FilterComboBox.Text,'SQMReader')) then begin
         try
           {$I+}

           //Default to no model selected
           PlotterModel:=0;

           //Default to no plot type selected
           PlotType:='';

           Reset(File1);
           repeat
             // Read one line at a time from the file.
             Readln(File1, Str);

             {Separate the fields of the record.}
             pieces.Delimiter := ',';
             pieces.DelimitedText := Str;

             {Set default MPSAS field}
             MSASField:=2;

             { Check for comment lines }
             if (AnsiStartsStr('Produced by SQM Reader',Str) or AnsiStartsStr('Year',Str)) then begin
               //Ignore these lines
             end

             else if (AnsiStartsStr('Date/Time,MPSAS',Str)) then begin
                 FS.ShortDateFormat := 'dd/mm/yyyy';
                 FS.ShortTimeFormat := 'hh:nn:ss';
                 MSASField:=1;
             end

             else begin

                 {Separate the fields of the record.}
                 pieces.DelimitedText := Str;

                 DateString:=pieces.Strings[0]+ ' ' +pieces.Strings[1];
                 LocalRecordTime:= StrToDateTime(DateString, FS);

                 PlotRecordTime:=LocalRecordTime;

                 //Update Chart
                 Darkness:= StrToFloatDef(AnsiLeftStr(pieces.Strings[MSASField],Length(pieces.Strings[MSASField])),0,FPointSeparator);

                   if PlotNumberGroup.ItemIndex=0 then
                      MPSASSeries.AddXY(PlotRecordTime, Darkness);
                   if PlotNumberGroup.ItemIndex=1 then
                      MPSASSeries2.AddXY(PlotRecordTime, Darkness);
                   if PlotNumberGroup.ItemIndex=2 then
                      MPSASSeries3.AddXY(PlotRecordTime, Darkness);

             end;//end of checking for data

           until(EOF(File1)); // EOF(End Of File) The the program will keep reading new lines until there is none.
           CloseFile(File1);

         except
           on E: EInOutError do
             MessageDlg('Error', 'File handling error occurred. Details: '+E.ClassName+'/'+E.Message, mtError, [mbOK],0);
           on E: Exception do
             ShowMessage( 'Error: '+ E.ClassName + #13#10 + '>' + str + '<' + #13#10 + E.Message );

         end;

       end //End of SQM Reader files

   else if (AnsiContainsStr(FilterComboBox.Text,'LightPollutionMap')) then begin
    try
      {$I+}

      //Default to no model selected
      PlotterModel:=0;

      //Default to no plot type selected
      PlotType:='';

      FS.ShortDateFormat := 'dd/mm/yyyy';
      FS.LongTimeFormat := 'hh:nn:ss';

      TemperaturePointerColor:=$00008000; //Inidicates 'subsequent' reading

      {Set default MPSAS field}
      MSASField:=1;
      TemperatureField:=2;

      Reset(File1);
      Readln(File1, Str);//**debug delete first line
      repeat
        // Read one line at a time from the file.
        Readln(File1, Str);

        {Separate the fields of the record.}
        pieces.Delimiter := ' ';
        pieces.DelimitedText := Str;

        {Separate the fields of the record.}
        pieces.DelimitedText := Str;

        {Get date from filename IN:yyyymmdd, OUT:dd/mm/yyyy}
        DateString:=AnsiRightStr(Utf8ToAnsi(ExtractFileNameOnly(PlotFileName)),2) + //date
                    '/'+AnsiMidStr(Utf8ToAnsi(ExtractFileNameOnly(PlotFileName)),5,2) + //Month
                    '/'+AnsiLeftStr(Utf8ToAnsi(ExtractFileNameOnly(PlotFileName)),4) + //Year
                    ' '+pieces.Strings[0];
        LocalRecordTime:= StrToDateTime(DateString, FS);

        PlotRecordTime:=LocalRecordTime;

        //Update Chart
        Darkness:= StrToFloatDef(AnsiLeftStr(pieces.Strings[MSASField],Length(pieces.Strings[MSASField])-1),0,FPointSeparator);
        Temperature:=StrToFloatDef(AnsiLeftStr(pieces.Strings[TemperatureField],Length(pieces.Strings[TemperatureField])-1),0,FPointSeparator);

        if PlotNumberGroup.ItemIndex=0 then
           MPSASSeries.AddXY(PlotRecordTime, Darkness);
        if PlotNumberGroup.ItemIndex=1 then
           MPSASSeries2.AddXY(PlotRecordTime, Darkness);
        if PlotNumberGroup.ItemIndex=2 then
           MPSASSeries3.AddXY(PlotRecordTime, Darkness);

        TemperatureSeries.AddXY(PlotRecordTime, Temperature, '', TemperaturePointerColor);

      until(EOF(File1)); // EOF(End Of File) The the program will keep reading new lines until there is none.
      CloseFile(File1);

    except
      on E: EInOutError do
        MessageDlg('Error', 'File handling error occurred. Details: '+E.ClassName+'/'+E.Message, mtError, [mbOK],0);
      on E: Exception do
        ShowMessage( 'Error: '+ E.ClassName + #13#10 + '>' + str + '<' + #13#10 + E.Message );

    end;

  end;//End of LightPollutionMap file


   end; //End of checking for .txt file type


   '.log': begin   //Old log files from Perl script
            {$I+}
            try

              //Get Lat, Lon for Moon calculations
              pieces.Delimiter := ',';
              //Separate the fields of the lat,lon.
              pieces.DelimitedText := PositionEntry.Text;

              MyLatitude:=StrToFloat(pieces.Strings[0],FPointSeparator);
              if ((MyLatitude<-90.0) or (MyLatitude>90.0)) then begin
                  MessageDlg('Latitude Error',
                    'Error: Latitude ' + FloatToStr(MyLatitude)+' out of range.',
                    mtError,
                    [mbOK],0);
              end;
              MyLongitude:=StrToFloat(pieces.Strings[1],FPointSeparator);
              if ((MyLongitude<-180.0) or (MyLongitude>180.0)) then begin
                  MessageDlg('Longitude Error',
                    'Error: Longitude ' + FloatToStr(MyLongitude)+' out of range.',
                    mtError,
                    [mbOK],0);
              end;


              Reset(File1);
              pieces.Delimiter := ' ';
              repeat
                // Read one line at a time from the file.
                Readln(File1, Str);

                //Separate the fields of the record.
                pieces.DelimitedText := Str;

                //Make sure there are enough fields
                if (pieces.Count<>3) then begin
                    MessageDlg('Error', 'Incorrect number of fields in record. Expected 3, got: '+ IntToStr(pieces.Count), mtError, [mbOK],0);
                    break;
                  end
                else begin
                  //parse the fields, and convert as necessary.
                  //Convert UTC string 'YYYY-MM-DDTHH:mm:ss.fff' into TDateTime
                  LocalRecordTime:=ScanDateTime('yyyymmddhh:nn:ss',LazFileUtils.ExtractFileNameOnly(PlotFileName) + pieces.Strings[0]);

                  UTCRecordTime := ptzAll.LocalTimeToGMT(LocalRecordTime,PlotterTZLocation);

                  //Convert local time to UTC for Moon calculation

                  //Update Chart
                  Darkness:= StrToFloatDef(AnsiLeftStr(pieces.Strings[1],Length(pieces.Strings[1])-1),0,FPointSeparator); //remove trailing m from mpsas
                  MPSASSeries.AddXY(LocalRecordTime, Darkness);
                  //Calculate Moon position
                  //Change sign for Moon calculations
                  Moon_Position_Horizontal(
                                          StrToDateTime(DateTimeToStr(UTCRecordTime)),
                                          -1.0*MyLongitude,
                                          MyLatitude,
                                          MoonElevation,
                                          MoonAzimuth);
                  MoonSeries.AddXY(LocalRecordTime,MoonElevation);
                  //Calculate Sun position
                  //Change sign for Sun calculations
                  Sun_Position_Horizontal(StrToDateTime(DateTimeToStr(UTCRecordTime)),
                                          -1.0*MyLongitude,
                                          MyLatitude,
                                          SunElevation,
                                          SunAzimuth);
                  SunSeries.AddXY(LocalRecordTime,SunElevation);

                end;//End of checking number of fields in record.

              until(EOF(File1)); // EOF(End Of File) The the program will keep reading new lines until there is none.
              CloseFile(File1);



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

            end;
   end;

   //New format .dat files from Log continuous and DL retrieve.
   '.dat': begin
             {$I+}
             try

                {Show default position}
               { Pull Timezone information from INI file if it exists.}
               PlotterTZRegion:=Trim(vConfigurations.ReadString(PlotterINIsection,'Local region'));

               { Read the previously recorded entries. }
               DefaultTZLocation:= Trim(vConfigurations.ReadString(PlotterINIsection,'Local time zone'));

               TZChanging:=True; //prevent change triggers
                 { Update timezone identifiers on screen. }
                 TZRegionBox.Text:=PlotterTZRegion;
                 TZLocationBox.Text:=DefaultTZLocation;
               TZChanging:=False;

               PlotterTZLocation:='';
               PlotFileTimezoneLabel.Text:=PlotterTZLocation;

               { Assume that Sun/Moon elevation labels are not required. }
               RequireSunMoonElevationLabel:=False;


               Application.ProcessMessages;

               PositionParse();

               //Default to no model selected
               PlotterModel:=0;

               //Default to no plot type selected
               PlotType:='';

               Reset(File1);
               repeat
                 // Read one line at a time from the file.
                 Readln(File1, Str);

                 //Check for comment lines
                 //Skip improper dates (like 1985)
                 if (AnsiStartsStr('#',Str) or AnsiStartsStr('1985',Str)) then begin

                  { *** Determine if file UTC and position match default values, warn. }
                   { Get the .dat timezone information if it exists}
                   if (AnsiStartsStr('# Local timezone:',str)) then begin
                     pieces.Delimiter := ':';
                     pieces.StrictDelimiter := True; //Do not parse spaces also
                     pieces.DelimitedText := Str;
                     if (pieces.Count>1) then
                       if Trim(pieces.Strings[1]) <>'' then begin
                         datLocalTZ:=Trim(pieces.Strings[1]);
                       end;
                     {Determine if zone is valid.}
                     if ((Length(datLocalTZ)>0) and (not ptzAll.TimeZoneExists(datLocalTZ,True))) then begin
                       if (ptzAll.TimeZoneExists(StringReplace(datLocalTZ,' ','_',[rfReplaceAll]),True)) then begin
                       ShowMessage(Format('Time zone [%s] %s%sin the file:%s %s%sshould not have spaces in name.',
                         [datLocalTZ,sLineBreak,sLineBreak,PlotFileName,sLineBreak,sLineBreak]));
                       end else
                       ShowMessage(Format('Time zone [%s] %s%sin the file:%s %s%sdoes not exist in the timezone database.',
                         [datLocalTZ,sLineBreak,sLineBreak,PlotFileName,sLineBreak,sLineBreak]));
                       datLocalTZ:='';
                     end;
                   end;

                   { Get Serial number (for location information) }
                    if (AnsiStartsStr('# SQM serial number:',str)) then begin
                       pieces.Delimiter := ':';
                       pieces.StrictDelimiter := True; //Do not parse spaces also
                       pieces.DelimitedText := Str;
                       if (pieces.Count>1) then
                         if Trim(pieces.Strings[1]) <>'' then begin
                           SelectedSerialNumberString:=Trim(pieces.Strings[1]);
                         end;

                       { Use .dat file serial number to get stored time zone information. }
                       SerialINIsection:='Serial:'+SelectedSerialNumberString;
                       MeterTZLocation:= Trim(vConfigurations.ReadString(SerialINIsection,'Local time zone'));
                       {Determine if zone is valid.}
                       if ((Length(MeterTZLocation)>0) and (not ptzAll.TimeZoneExists(MeterTZLocation,True))) then begin
                         ShowMessage(Format('Time zone [%s] %s in the config file for this meter [Serial:%s]%s does not exist in timezone database.',
                           [MeterTZLocation,sLineBreak,SelectedSerialNumberString,sLineBreak]));
                         MeterTZLocation:='';
                       end;

                    end;

                  { Parse position definition line. }
                   if (AnsiStartsStr('# Position',str)) then begin
                      pieces.Delimiter := ':';
                      pieces.StrictDelimiter := True; //Do not parse spaces also
                      pieces.DelimitedText := Str;
                      if (pieces.Count>1) then begin
                        if Trim(pieces.Strings[1]) <>'' then begin
                          PositionEntry.Text:=Trim(pieces.Strings[1]);
                          PositionParse();
                        end;
                      end;
                   end;

                   { Parse the model type. }
                   if (AnsiStartsStr('# SQM firmware version:',str)) then begin
                      pieces.Delimiter := ':';
                      pieces.StrictDelimiter := True; //Do not parse spaces also
                      pieces.DelimitedText := Str;
                      if (pieces.Count>1) then begin
                        FirmwareString:=pieces.Strings[1];
                        pieces.Delimiter := '-';
                        pieces.StrictDelimiter := True; //Do not parse spaces also
                        pieces.DelimitedText := FirmwareString;
                         if (pieces.Count=3) then begin
                           PlotterModel:= StrToIntDef(pieces.Strings[1],0);
                           PlotterModelText.Caption:=IntToStr(PlotterModel);
                         end;
                      end;
                   end;

                   { Parse field definition line. }
                   if (AnsiContainsStr(str,'UTC') and AnsiContainsStr(str,'Local') and AnsiContainsStr(str,'MSAS')) then begin
                     pieces.Delimiter := ',';
                     pieces.StrictDelimiter := True; //Do not parse spaces also
                     pieces.DelimitedText := Str;

                     { Get the field locations. }
                     for i:=0 to pieces.Count-1 do begin
                       case Trim(pieces.Strings[i]) of
                         'MSAS': MSASField:=i;
                         'Counts': CountsField:=i;
                         'Scale': ScaleField:=i;
                         'Color': ColorField:=i;
                         'Temperature': TemperatureField:=i;
                         'Voltage': VoltageField:=i;
                         'Record type': RecordTypeField:=i;
                         'Std lin.': StdLinField:=i;
                         'Snow MSAS': SnowMSASField:=i;
                         'Snow lin.': SnowLinField:=i;
                         'SunriseDiff':SunriseDiffFIeld:=i;
                       end;
                     end;
                     if ((PlotterModel=4) and (ScaleField>0) and (ColorField>0)) then begin
                         PlotType:='Color';
                         PlotterModelType.Caption:=PlotType;
                         PlotterSN.Caption:=SelectedSerialNumberString;
                         readingstextR.Visible:=True;
                         readingstextG.Visible:=True;
                         readingstextB.Visible:=True;
                         readingstextC.Visible:=True;
                         ColorSwatch.Visible:=True;
                         VoltageText.Visible:=False;
                         VoltageTextLabel.Visible:=False;
                         Chart1.AxisList[1].Marks.Format:='%0:3.0f';
                       end else begin
                         case PlotterModel of
                           3: PlotType:='LE/LU';
                           5: PlotType:='LR';
                           6: PlotType:='DL';
                           11:PlotType:='V';
                           else PlotType:='';
                         end;
                          PlotterModelType.Caption:=PlotType;
                          PlotterSN.Caption:=SelectedSerialNumberString;
                          readingstextR.Visible:=False;
                          readingstextG.Visible:=False;
                          readingstextB.Visible:=False;
                          readingstextC.Visible:=False;
                          ColorSwatch.Visible:=False;
                          VoltageText.Visible:=True;
                          VoltageTextLabel.Visible:=True;
                          Chart1.AxisList[1].Marks.Format:='%0:3.2f';
                       end;

                       ReadingText.Visible:=DarknessVisible;
                       ReadingTextLabel.Visible:=DarknessVisible;

                       SunText.Visible:=SunVisible;
                       SunTextLabel.Visible:=SunVisible;

                       MoonText.Visible:=MoonVisible;
                       MoonTextLabel.Visible:=MoonVisible;

                       VoltageText.Visible:=VoltageVisible;
                       VoltageTextLabel.Visible:=VoltageVisible;

                       TemperatureReadingText.Visible:=TemperatureVisible;
                       TemperatureReadingTextLabel.Visible:=TemperatureVisible;

                   end;

                   {Finished reading header/comment lines.}
                   if (AnsiStartsStr('# END OF HEADER',str)) then begin

                     {Assumptions}
                     Plottable:=True; // Plottable assumed.
                     PlotterTZLocation:='';// No time zone iniitally selected.

                     {Load up desired timezone. the selection priority is:
                        1. .dat value (datLocalTZ)
                        2. meter serial associated value (MeterTZLocation)
                        3. stored default (DefaultTZLocation)
                      }

                     if (Length(datLocalTZ)>0) then
                       PlotterTZLocation:=datLocalTZ else
                       if (Length(MeterTZLocation)>0) then
                         PlotterTZLocation:=MeterTZLocation else
                         if (Length(DefaultTZLocation)>0) then
                           PlotterTZLocation:=DefaultTZLocation else begin
                             ShowMessage('Enter timezone.');
                             Plottable:=False;
                           end;

                     {Determine if zone is valid.}
                     if (not ptzAll.TimeZoneExists(PlotterTZLocation,True)) then begin
                       ShowMessage(Format('Time zone [%s] does not exist.',[PlotterTZLocation]));
                       Plottable:=False;
                     end;

                     PlotFileTimezoneLabel.Text:=PlotterTZLocation;
                     Application.ProcessMessages;

                   end;


                 end else begin


                   if Plottable then begin

                   //Separate the fields of the record.
                   pieces.Delimiter := ';';
                   pieces.DelimitedText := Str;

                   //Separate the fields of the record.
                   pieces.DelimitedText := Str;


                   //Make sure there are enough fields
                   if (MSASField=-1) then begin

                       DialogCentered(
                         'Error',
                         'No MSAS field defined in file',
                         mtError,
                         [mbOK],
                         0,
                         PlotterForm
                        );

                       break;
                     end
                   else begin

                     UTCRecordTime  :=ScanDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz',pieces.Strings[0]);
                     LocalRecordTime:=ScanDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz',pieces.Strings[1]);

                     if YearOf(UTCRecordTime)>2000 then begin

                     //Time reference for plotting.
                     case TimeReference of
                       'UTC': PlotRecordTime:=UTCRecordTime;
                       else PlotRecordTime:=LocalRecordTime;
                     end;

                     {Add manual time offset to this data set}
                     if TimeOffset<>0 then begin
                       UTCRecordTime  :=IncSecond(  UTCRecordTime, TimeOffset);
                       LocalRecordTime:=IncSecond(LocalRecordTime, TimeOffset);
                       PlotRecordTime :=IncSecond( PlotRecordTime, TimeOffset);
                     end;

                     {Get range of time to plot Sun, Moon, Twilights}
                     If (LocalRecordTime>MaxLocalRecordTime) then
                       MaxLocalRecordTime:=LocalRecordTime;
                     If ((LocalRecordTime<MinLocalRecordTime) or (MinLocalRecordTime=0)) then
                       MinLocalRecordTime:=LocalRecordTime;

                     //Update Chart
                     {Temperature}
                     if TemperatureField>-1 then begin
                       Temperature := StrToFloatDef(pieces.Strings[TemperatureField],0,FPointSeparator);
                     end;

                     {Voltage}
                     if VoltageField>-1 then
                       Voltage:= StrToFloatDef(pieces.Strings[VoltageField],0,FPointSeparator);

                     if SunriseDiffField>-1 then begin
                       SunriseDifference:=StrToFloatDef(pieces.Strings[SunriseDiffField],0,FPointSeparator);
                       SunriseDiffVisible:=True;
                       SunriseDifferenceLabel.visible:=True;
                       SunriseDifferenceText.visible:=True;
                       Chart1.AxisList[5].Visible:=True;
                       end
                     else begin
                       SunriseDiffVisible:=False;
                       SunriseDifferenceLabel.visible:=False;
                       SunriseDifferenceText.visible:=False;
                       Chart1.AxisList[5].Visible:=False;
                     end;

                     {RecordTypeField}
                     if ((RecordTypeField>-1) and (pieces.Count>RecordTypeField)) then begin
                       RecordType:= StrToIntDef(pieces.Strings[RecordTypeField],0);
                     end else
                       RecordType:= 1; //Assume subsequent record.

                     {Darkness}

                     { - Color model, only display counts since very dark mpsas might be messed up}
                     //if (PlotType='Color') then
                     //  MSASField:=CountsField;

                     if pieces.Count>MSASField then begin
                       Darkness:= StrToFloatDef(AnsiLeftStr(pieces.Strings[MSASField],Length(pieces.Strings[MSASField])),0,FPointSeparator);

                       {Gather darknesses for color model}
                       if (PlotType='Color') then begin
                         if pieces.Count>ColorField then begin
                          case StrToIntDef(pieces.Strings[ColorField],0) of
                            0: LastRedDarkness  :=StrToFloatDef(AnsiLeftStr(pieces.Strings[CountsField],Length(pieces.Strings[CountsField])),0,FPointSeparator);
                            1: LastBlueDarkness :=StrToFloatDef(AnsiLeftStr(pieces.Strings[CountsField],Length(pieces.Strings[CountsField])),0,FPointSeparator);
                            2: LastClearDarkness:=StrToFloatDef(AnsiLeftStr(pieces.Strings[CountsField],Length(pieces.Strings[CountsField])),0,FPointSeparator);
                            3: LastgreenDarkness:=StrToFloatDef(AnsiLeftStr(pieces.Strings[CountsField],Length(pieces.Strings[CountsField])),0,FPointSeparator);
                          end;
                         end;
                       end;
                     end else begin
                       Darkness:=0;
                     end;

                     if SnowMSASField>-1 then
                       SnowMSAS:=StrToFloatDef(pieces.Strings[SnowMSASField],0,FPointSeparator);

                       case FileSelectionMode of
                         'Single','Accumulate': begin

                           { Colorize pointer label depending on initial/subsequent reading.
                             Pointer style is also changed depending on color in the OngetPointerStyle handler.}
                           if RecordType=0 then begin {initial record}
                             if ContinuousLine then begin
                               MPSASPointerColor:=clRed; {Inidicates 'continuous' reading.}
                               VoltagePointerColor:=clBlue; {Inidicates 'continuous' reading.}
                               TemperaturePointerColor:=$00008000; {Inidicates 'continuous' reading.}
                             end
                             else begin
                               {Leave a blank unconnected space between lines.}
                               MPSASPointerColor:=clWhite; //Inidicates 'initial' reading
                               VoltagePointerColor:=clWhite; //Inidicates 'initial' reading
                               TemperaturePointerColor:=clWhite; //Inidicates 'initial' reading
                               LastPlotRecordTime:=0;
                               {Leave a blank unconnected space between subsequent to initial record.}
                               if DarknessVisible then MPSASSeries.AddNull();
                               if VoltageVisible then VoltSeries.AddNull();
                               if TemperatureVisible then TemperatureSeries.AddNull();
                               if MoonVisible then MoonSeries.AddNull();
                               if SunVisible then SunSeries.AddNull();
                             end;
                           end else begin {subsequent record}
                             MPSASPointerColor:=clRed; //Inidicates 'subsequent' reading
                             VoltagePointerColor:=clBlue; //Inidicates 'subsequent' reading
                             TemperaturePointerColor:=$00008000; //Inidicates 'subsequent' reading
                             {Check if line should be drawn if current_span is  <= last_span}
                             {Check if darkness is zero.}
                             if (
                                 ((MinuteSpan(LastPlotRecordTime,PlotRecordTime)>LastRecordMinuteSpan+1)
                                 and not ContinuousLine)
                                 or (not ZeroPlot and ((Darkness=0) or (LastDarkness=0)))
                                ) then begin
                               {Leave a blank unconnected space between lines.}
                               if DarknessVisible then MPSASSeries.AddNull();
                             end;
                             if (((MinuteSpan(LastPlotRecordTime,PlotRecordTime)>LastRecordMinuteSpan+1) and not ContinuousLine)) then begin
                               {Leave a blank unconnected space between lines.}
                               if VoltageVisible then VoltSeries.AddNull();
                               if TemperatureVisible then TemperatureSeries.AddNull();
                               if MoonVisible then MoonSeries.AddNull();
                               if SunVisible then SunSeries.AddNull();
                             end;
                             LastRecordMinuteSpan:=MinuteSpan(LastPlotRecordTime,PlotRecordTime);
                             LastPlotRecordTime:=PlotRecordTime;
                           end;

                           {Determine pointer symbol for each record}
                           if RecordType=0 then //Initial reading
                             symbol:=psRectangle
                           else if Darkness=0 then //zero reading (saturated brightness)
                             symbol:=psPoint
                           else
                             symbol:=psNone; //Subsequent readings have a clean connecting line.

                           if ContinuousLine then
                              symbol:=psNone; //Override pointer symbol when in continuous line mode.

                           if (PlotType='Color') then begin  {Color model}
                               RedSeries.AddXY(PlotRecordTime, LastRedDarkness);
                               GreenSeries.AddXY(PlotRecordTime, LastgreenDarkness);
                               BlueSeries.AddXY(PlotRecordTime, LastBlueDarkness);
                               ClearSeries.AddXY(PlotRecordTime, LastClearDarkness);
                             end
                           else begin {non-color model}
                             j:=MPSASSeries.AddXY(PlotRecordTime, Darkness,'', MPSASPointerColor);
                             {Record darkness for future comparisons}
                             LastDarkness:=Darkness;
                             MPSASSeries.XValues[j, 1] := ord(symbol);
                           end;

                           if VoltageVisible then
                              VoltSeries.AddXY(PlotRecordTime, Voltage, '', VoltagePointerColor);

                           if TemperatureVisible then
                              TemperatureSeries.AddXY(PlotRecordTime, Temperature, '', TemperaturePointerColor);

                           if SnowMSASField>-1 then
                             SnowSeries.AddXY(PlotRecordTime, SnowMSAS, '', clBlack);

                           if SunriseDiffVisible then
                             SunriseDifferenceSeries.AddXY(PlotRecordTime, SunriseDifference, '', clBlack);

                         end;

                         'Multiple': MultiChart[MultiIndex].AddXY(PlotRecordTime, Darkness);
                       end;

                     {Check if position is usable for Moon/Sun plotting}
                      if PositionUsable then begin

                        {Check if Moon plot has been selected}
                        if MoonVisible then begin;
                          { Calculate Moon position:
                              - Change longitude sign for astronomy calculations. }
                           Moon_Position_Horizontal(
                                                   StrToDateTime(DateTimeToStr(UTCRecordTime)),
                                                   -1.0*MyLongitude,
                                                   MyLatitude,
                                                   MoonElevation,
                                                   MoonAzimuth);
                           if ((not ClipSunMoon) or (MoonElevation>=-18)) then begin
                            MoonSeries.AddXY(PlotRecordTime,MoonElevation);
                            RequireSunMoonElevationLabel:=True;
                           end else
                             MoonSeries.AddNull;

                         end;

                        {Check if Sun plot has been selected}
                        if SunVisible then begin;
                           { Calculate Sun position:
                               -  Change longitude sign for astronomy calculations. }
                           Sun_Position_Horizontal(StrToDateTime(DateTimeToStr(UTCRecordTime)),
                                                   -1.0*MyLongitude,
                                                   MyLatitude,
                                                   SunElevation,
                                                   SunAzimuth);
                          if ((not ClipSunMoon) or (SunElevation>=-18)) then begin
                           SunSeries.AddXY(PlotRecordTime,SunElevation);
                           RequireSunMoonElevationLabel:=True;
                          end else
                           SunSeries.AddNull;
                        end;

                      end; // End of Checking if position is usable for Moon/Sun plotting
                     end;//End of checking for valid year in timestamp
                   end;//End of checking number of fields in record.

                  end;//End of plottable section
                 end;//end of checking for data

               until(EOF(File1)); // EOF(End Of File) The the program will keep reading new lines until there is none.
               CloseFile(File1);

             except
               on E: EInOutError do
                 MessageDlg('Error', 'File handling error occurred. Details: '+E.ClassName+'/'+E.Message, mtError, [mbOK],0);
               on E: Exception do begin
                 ShowMessage( 'Error: '+ E.ClassName + #13#10 + '>' + str + '<' + #13#10 + E.Message );
               end;

             end;

             { Only plot rise/set times if UTC and position are defined }
             if (Plottable and PositionUsable) then begin
               RequireSunMoonElevationLabel:=True;

               {--- Plot twilight lines ---
                 Twilight times for all recorded range is plotted. }
               PlotLocalRecordDate:=DateOf(MinLocalRecordTime); //Starting point
               MaxLocalRecordDate := DateOf(MaxLocalRecordTime); //Ending point

               {Create time range for visible twilight lines}
               case TimeReference of
                    'UTC': begin
                      MinPlotTime:=ptzAll.LocalTimeToGMT(MinLocalRecordTime,PlotterTZLocation);
                      MaxPlotTime:=ptzAll.LocalTimeToGMT(MaxLocalRecordTime,PlotterTZLocation);
                    end
                    else begin
                      MinPlotTime:=MinLocalRecordTime;
                      MaxPlotTime:=MaxLocalRecordTime;
                    end;
               end;

               { Rescale X axis because use of MPSAS flags stored in X[1] caused bad x axis ranging.}
               Chart1.Extent.XMin:=MinPlotTime;
               Chart1.Extent.UseXMin:=True;

               while (PlotLocalRecordDate<=MaxLocalRecordDate+1) do begin
                   {Get UTC value for this Local date}
                   try
                     PlotUTCRecordDate := ptzAll.LocalTimeToGMT(PlotLocalRecordDate,PlotterTZLocation);
                   except
                     on E: Exception do begin
                       ShowMessage( 'Error: '+ E.ClassName + #13#10 + '>' + str + '<' + #13#10 + E.Message );
                     end;
                   end;

                   try

                     if SunTwilightVisible then begin
                      case TimeReference of
                        'UTC': begin //UTC time stamps
                            TwilightMorningSunTimeStamp:=Sun_Rise(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude);
                            TwilightEveningSunTimeStamp:=Sun_Set(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude);
                        end;
                        else begin //local time stamps
                            TwilightMorningSunTimeStamp:=ptzAll.GMTToLocalTime(Sun_Rise(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude),PlotterTZLocation, subfix);
                            TwilightEveningSunTimeStamp:=ptzAll.GMTToLocalTime(Sun_Set(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude),PlotterTZLocation, subfix);
                        end;
                      end;
                      { Check if computed twilight line falls within min/max record time }
                      if ((TwilightMorningSunTimeStamp>=MinPlotTime) and (TwilightMorningSunTimeStamp<=MaxPlotTime)) then begin
                       {Plot morning Sunrise line}
                       SunTwilightSeries.AddNull();
                       SunTwilightSeries.AddXY(TwilightMorningSunTimeStamp,TwilightMax,'',clDefault);
                       SunTwilightSeries.AddXY(TwilightMorningSunTimeStamp,TwilightMin,'',clDefault);
                      end;
                      if ((TwilightEveningSunTimeStamp>=MinPlotTime) and (TwilightEveningSunTimeStamp<=MaxPlotTime)) then begin
                       {Plot evening Sunset line}
                       SunTwilightSeries.AddNull();
                       SunTwilightSeries.AddXY(TwilightEveningSunTimeStamp,TwilightMin,'',clDefault);
                       SunTwilightSeries.AddXY(TwilightEveningSunTimeStamp,TwilightMax,'',clDefault);
                      end;
                     end;

                     {Plot morning Civil point}
                     if CivilTwilightVisible then begin
                      case TimeReference of
                        'UTC': begin //UTC time stamps
                            TwilightMorningCivilTimeStamp:=Morning_Twilight_Civil(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude);
                            TwilightEveningCivilTimeStamp:=Evening_Twilight_Civil(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude);
                        end;
                        else begin //local time stamps
                            TwilightMorningCivilTimeStamp:=ptzAll.GMTToLocalTime(Morning_Twilight_Civil(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude),PlotterTZLocation, subfix);
                            TwilightEveningCivilTimeStamp:=ptzAll.GMTToLocalTime(Evening_Twilight_Civil(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude),PlotterTZLocation, subfix);
                        end;
                      end;
                      { Check if computed twilight line falls within min/max record time }
                      if ((TwilightMorningCivilTimeStamp>=MinPlotTime) and (TwilightMorningCivilTimeStamp<=MaxPlotTime)) then begin
                       {Plot morning Civil line}
                       CivilTwilightSeries.AddNull();
                       CivilTwilightSeries.AddXY(TwilightMorningCivilTimeStamp,TwilightMax,'',clDefault);
                       CivilTwilightSeries.AddXY(TwilightMorningCivilTimeStamp,TwilightMin,'',clDefault);
                      end;
                      if ((TwilightEveningCivilTimeStamp>=MinPlotTime) and (TwilightEveningCivilTimeStamp<=MaxPlotTime)) then begin
                      {Plot evening Civil point}
                       CivilTwilightSeries.AddNull();
                       CivilTwilightSeries.AddXY(TwilightEveningCivilTimeStamp,TwilightMin,'',clDefault);
                       CivilTwilightSeries.AddXY(TwilightEveningCivilTimeStamp,TwilightMax,'',clDefault);
                      end;
                     end;

                     {Plot morning Nautical point}
                     if NauticalTwilightVisible then begin
                      case TimeReference of
                        'UTC': begin //UTC time stamps
                            TwilightMorningNauticalTimeStamp:=Morning_Twilight_Nautical(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude);
                            TwilightEveningNauticalTimeStamp:=Evening_Twilight_Nautical(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude);
                        end;
                        else begin //local time stamps
                            TwilightMorningNauticalTimeStamp:=ptzAll.GMTToLocalTime(Morning_Twilight_Nautical(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude),PlotterTZLocation, subfix);
                            TwilightEveningNauticalTimeStamp:=ptzAll.GMTToLocalTime(Evening_Twilight_Nautical(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude),PlotterTZLocation, subfix);
                        end;
                      end;
                      { Check if computed twilight line falls within min/max record time }
                      if ((TwilightMorningNauticalTimeStamp>=MinPlotTime) and (TwilightMorningNauticalTimeStamp<=MaxPlotTime)) then begin
                         {Plot morning Nautical line}
                         NauticalTwilightSeries.AddNull();
                         NauticalTwilightSeries.AddXY(TwilightMorningNauticalTimeStamp,TwilightMax,'',clDefault);
                         NauticalTwilightSeries.AddXY(TwilightMorningNauticalTimeStamp,TwilightMin,'',clDefault);
                       end;
                      if ((TwilightEveningNauticalTimeStamp>=MinPlotTime) and (TwilightEveningNauticalTimeStamp<=MaxPlotTime)) then begin
                         {Plot evening Nautical point}
                         NauticalTwilightSeries.AddNull();
                         NauticalTwilightSeries.AddXY(TwilightEveningNauticalTimeStamp,TwilightMin,'',clDefault);
                         NauticalTwilightSeries.AddXY(TwilightEveningNauticalTimeStamp,TwilightMax,'',clDefault);
                       end;
                     end;

                     {Plot morning Astronmical point}
                     if AstronomicalTwilightVisible then begin
                      case TimeReference of
                        'UTC': begin //UTC time stamps
                            TwilightMorningAstronomicalTimeStamp:=Morning_Twilight_Astronomical(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude);
                            TwilightEveningAstronomicalTimeStamp:=Evening_Twilight_Astronomical(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude);
                        end;
                        else begin //local time stamps
                            TwilightMorningAstronomicalTimeStamp:=ptzAll.GMTToLocalTime(Morning_Twilight_Astronomical(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude),PlotterTZLocation, subfix);
                            TwilightEveningAstronomicalTimeStamp:=ptzAll.GMTToLocalTime(Evening_Twilight_Astronomical(PlotUTCRecordDate,MyLatitude,-1.0 * MyLongitude),PlotterTZLocation, subfix);
                        end;
                      end;
                      { Check if computed twilight line falls within min/max record time }
                      if ((TwilightMorningAstronomicalTimeStamp>=MinPlotTime) and (TwilightMorningAstronomicalTimeStamp<=MaxPlotTime)) then begin
                         {Plot morning Astronomical line}
                         AstronmicalTwilightSeries.AddNull();
                         AstronmicalTwilightSeries.AddXY(TwilightMorningAstronomicalTimeStamp,TwilightMax,'',clDefault);
                         AstronmicalTwilightSeries.AddXY(TwilightMorningAstronomicalTimeStamp,TwilightMin,'',clDefault);
                       end;
                      if ((TwilightEveningAstronomicalTimeStamp>=MinPlotTime) and (TwilightEveningAstronomicalTimeStamp<=MaxPlotTime)) then begin
                         {Plot evening Astronmical point}
                         AstronmicalTwilightSeries.AddNull();
                         AstronmicalTwilightSeries.AddXY(TwilightEveningAstronomicalTimeStamp,TwilightMin,'',clDefault);
                         AstronmicalTwilightSeries.AddXY(TwilightEveningAstronomicalTimeStamp,TwilightMax,'',clDefault);
                      end;
                    end;

                   except
                     StatusMessage('Plotter: Twilight calculation exception');
                   end;

                   {Next day}
                   PlotLocalRecordDate:=IncDay(PlotLocalRecordDate);
               end;
               {--- End: Plot twilight lines ---}

             end; //end of plottable twilight

             { Show Sun/Moon elevation axis if necessary}
             Chart1.AxisList[2].Visible:= (SunVisible or MoonVisible);


   end;//End of .dat plotting

   else begin end;

   end;
   screen.Cursor:= crDefault;
   if Assigned(pieces) then FreeAndNil(pieces);

end;

{ User requests file to be plotted }
procedure TPlotterForm.PlotFilesStringGridClick(Sender: TObject);
begin
   ReplotButtonClick(nil);
end;

{ Custom sorting routine for string/number/date}
procedure TPlotterForm.PlotFilesStringGridCompareCells(Sender: TObject; ACol, ARow,
  BCol, BRow: Integer; var Result: integer);
begin

  { Initially (without an actual click), the SortColumn is -1. And,
    any program initiated sort done then will still call this routine.}

  case PlotFilesStringGrid.SortColumn of
  -1,0: Result:=CompareText(PlotFilesStringGrid.Cells[ACol,ARow],PlotFilesStringGrid.Cells[BCol,BRow]);
  1: begin //numeric sort for column 1 (numerical)
      // Result will be either <0, =0, or >0 for normal order.
        result := StrToIntDef(PlotFilesStringGrid.Cells[ACol,ARow],0)-StrToIntDef(PlotFilesStringGrid.Cells[BCol,BRow],0);
     end;
  2: begin //Date compare
      try  //In case date is unreadable or empty
        result := DateTimeToUnix(StrToDateTime(PlotFilesStringGrid.Cells[ACol,ARow],FDateSettings))
               - DateTimeToUnix(StrToDateTime(PlotFilesStringGrid.Cells[BCol,BRow],FDateSettings));
      except
        StatusMessage('Plotter: PlotFilesStringGridCompareCells exception');
      end;
     end;
  end;

  // For inverse order, just negate the result (eg. based on grid's SortOrder).
  if PlotFilesStringGrid.SortOrder = soDescending then
     result := -result;

end;

{ User requests sorting }
procedure TPlotterForm.PlotFilesStringGridHeaderClick(Sender: TObject;
  IsColumn: Boolean; Index: Integer);
begin
   if IsColumn then
    PlotFilesStringGrid.SortColRow(True, Index);

end;

procedure TPlotterForm.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(
          PlotFileDirectory+
          DirectorySeparator+
          ExtractFileNameOnly(SelectedFilename)+
          '.'+
          FileType
          );

  //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 Chart1 do
                Draw(id, Rect(0, 0, Width, Height));
            finally
              fs.Free;
            end;
         end;

         'png':   Chart1.SaveToFile(TPortableNetworkGraphic, fn);

    end;

    { Indicate where plot file got saved }
    ShowMessage('File saved to:"' + sLineBreak + fn );
  end;
end;

procedure TPlotterForm.CivilTwilightCheckBoxEditingDone(Sender: TObject);
begin
  CivilTwilightVisible:=CivilTwilightCheckBox.Checked;
  {Save setting for future}
  vConfigurations.WriteBool(PlotterINIsection,'CivilTwilight',CivilTwilightVisible);

  if CivilTwilightVisible then
     CivilTwilightSeries.LinePen.Style:=psSolid
  else
     CivilTwilightSeries.LinePen.Style:=psClear;
end;

procedure TPlotterForm.ContinuousLineCheckBoxEditingDone(Sender: TObject);
begin
  ContinuousLine:=ContinuousLineCheckBox.Checked;
  {Save setting for future}
  vConfigurations.WriteBool(PlotterINIsection,'ContinuousLine',ContinuousLine);
end;

procedure TPlotterForm.DarknessCheckBoxEditingDone(Sender: TObject);
begin
  if not UpdatingGUI then begin
    DarknessVisible:=DarknessCheckBox.Checked;
    {Save setting for future}
    vConfigurations.WriteBool(PlotterINIsection,'DarknessVisible',DarknessVisible);

    ReadingText.Visible:=DarknessVisible;
    ReadingTextLabel.Visible:=DarknessVisible;

    if DarknessVisible then begin
     MPSASSeries.LinePen.Style:=psSolid;
     MPSASSeries2.LinePen.Style:=psSolid;
     MPSASSeries3.LinePen.Style:=psSolid;
    end
    else begin
     MPSASSeries.LinePen.Style:=psClear;
     MPSASSeries2.LinePen.Style:=psClear;
     MPSASSeries3.LinePen.Style:=psClear;
    end;
  end;
end;

procedure TPlotterForm.AstronomicalTwilightCheckBoxEditingDone(Sender: TObject);
begin
  AstronomicalTwilightVisible:=AstronomicalTwilightCheckBox.Checked;
  {Save setting for future}
  vConfigurations.WriteBool(PlotterINIsection,'AstronomicalTwilight',AstronomicalTwilightVisible);

  if AstronomicalTwilightVisible then
     AstronmicalTwilightSeries.LinePen.Style:=psSolid
  else
     AstronmicalTwilightSeries.LinePen.Style:=psClear;
end;

procedure TPlotterForm.DayUpDownClick(Sender: TObject; Button: TUDBtnType);
begin
if Button=btNext then
 inc(TimeOffset,86400)
 else
 dec(TimeOffset,86400);

TimeOffsetSpinEdit.Text:=IntToStr(TimeOffset);

ReplotButtonClick(nil);
end;

procedure TPlotterForm.HideControlPanelButtonClick(Sender: TObject);
begin
  //Hide control panel
  ControlButtonPanel.Visible:=True;
  ControlPanel.Visible:=False;
end;

procedure TPlotterForm.ControlPanelButtonClick(Sender: TObject);
begin
  //View control panel
 ControlPanel.Visible:=True;
 ControlButtonPanel.Visible:=False;
end;

procedure TPlotterForm.HelpButtonClick(Sender: TObject);
begin
  //dialog box with help information
  MessageDlg(
    'Movement of charft using the mouse:'+ sLineBreak +
    '  - Pan = Right-click then drag.' + sLineBreak +
    '  - Zoom window =  Draw box from upper-left to lower-right.' + sLineBreak +
    '  - Zoom in/out = Scroll-wheel, or two-finger touchpad drag.' + sLineBreak  +
    '  - Reset size = Left click.' + sLineBreak  + sLineBreak +
    'Twilight rise/st lines are not enabled unless position is valid.' + sLineBreak + sLineBreak +
    'Twilight rise/set colors:' + sLineBreak +
    '  - Light Blue =Sunrise/set' + sLineBreak +
    '  - Blue = Civil twilight' + sLineBreak +
    '  - Navy Blue = Nautical twilight' + sLineBreak +
    '  - Black = Astronomical twilight' + sLineBreak + sLineBreak  +
    'Plot line colors (default)' + sLineBreak +
    '  - Black curved line = Moon plot' + sLineBreak +
    '  - Orange curved line = Sun plot' + sLineBreak +
    '  - Red plot line = Magnitudes per square arcsecond' + sLineBreak +
    '  - Green plot line = Temperature' + sLineBreak +
    '  - Blue plot line = Voltage'
    , mtInformation,[mbOK],0);

end;

procedure TPlotterForm.ChartToolset1PanDragTool1AfterMouseUp(ATool: TChartTool;
  APoint: TPoint);
begin
  { TODO : try to reset sun/moon y axis }
  //SunMoonChartAxisTransformationsAutoScaleAxisTransform1.;
end;

procedure TPlotterForm.HourUpDownClick(Sender: TObject; Button: TUDBtnType);
begin
  if Button=btNext then
   inc(TimeOffset,3600)
   else
   dec(TimeOffset,3600);

  TimeOffsetSpinEdit.Text:=IntToStr(TimeOffset);

  ReplotButtonClick(nil);

end;

procedure TPlotterForm.MinuteUpDownClick(Sender: TObject; Button: TUDBtnType);
begin
 if Button=btNext then
  inc(TimeOffset,60)
  else
  dec(TimeOffset,60);

 TimeOffsetSpinEdit.Text:=IntToStr(TimeOffset);

 ReplotButtonClick(nil);
end;

procedure TPlotterForm.TimeOffsetSpinEditChange(Sender: TObject);
begin
  TimeOffset:=StrToInt64Def(TimeOffsetSpinEdit.Text,0);
  //removed because replotting takes a long time for simple one second increments, and also the value cannot be entered manually since it triggers the change event.
  //ReplotButtonClick(nil);
end;

procedure TPlotterForm.ClipSunMoonCheckBoxEditingDone(Sender: TObject);
begin
  ClipSunMoon:=ClipSunMoonCheckBox.Checked;
  {Save setting for future}
  vConfigurations.WriteBool(PlotterINIsection,'ClipSunMoon',ClipSunMoon);
end;

procedure TPlotterForm.NauticalTwilightCheckBoxEditingDone(Sender: TObject);
begin
  NauticalTwilightVisible:=NauticalTwilightCheckBox.Checked;
  {Save setting for future}
  vConfigurations.WriteBool(PlotterINIsection,'NauticalTwilight',NauticalTwilightVisible);

  if NauticalTwilightVisible then
     NauticalTwilightSeries.LinePen.Style:=psSolid
  else
     NauticalTwilightSeries.LinePen.Style:=psClear;
end;

procedure TPlotterForm.SavePNGButtonClick(Sender: TObject);
begin
  SaveChart('png');
end;

procedure TPlotterForm.SaveSVGButtonClick(Sender: TObject);
begin
  SaveChart('svg');
end;

procedure TPlotterForm.SunCheckBoxEditingDone(Sender: TObject);
begin
  if not UpdatingGUI then begin
    SunVisible:=SunCheckBox.Checked;
    {Save setting for future}
    vConfigurations.WriteBool(PlotterINIsection,'Sun',SunVisible);

    if SunVisible then begin
      SunSeries.LinePen.Style:=psSolid;
      SunSeries.LinePen.Width:=1;
    end else begin
      SunSeries.LinePen.Style:=psClear;
      SunSeries.LinePen.Width:=0;
    end;

    SunText.Visible:=SunVisible;
    SunTextLabel.Visible:=SunVisible;

    Chart1.AxisList[2].Visible:= (SunVisible or MoonVisible);

  end;
end;

procedure TPlotterForm.SunTwilightCheckBoxEditingDone(Sender: TObject);
begin
  SunTwilightVisible:=SunTwilightCheckBox.Checked;
  {Save setting for future}
  vConfigurations.WriteBool(PlotterINIsection,'SunTwilight',SunTwilightVisible);

  if SunTwilightVisible then
     SunTwilightSeries.LinePen.Style:=psSolid
  else
     SunTwilightSeries.LinePen.Style:=psClear;
end;

procedure TPlotterForm.TemperatureCheckBoxEditingDone(Sender: TObject);
begin
  if not UpdatingGUI then begin
    TemperatureVisible:=TemperatureCheckBox.Checked;
    {Save setting for future}
    vConfigurations.WriteBool(PlotterINIsection,'Temperature',TemperatureVisible);

    TemperatureReadingText.Visible:=TemperatureVisible;
    TemperatureReadingTextLabel.Visible:=TemperatureVisible;
    Chart1.AxisList[4].Visible:=TemperatureVisible;

    if TemperatureVisible then
     TemperatureSeries.LinePen.Style:=psSolid
    else
     TemperatureSeries.LinePen.Style:=psClear;
  end;

end;

procedure TPlotterForm.TimeRefGroupClick(Sender: TObject);
begin
   if not UpdatingGUI then begin
    if TimeRefGroup.ItemIndex=0 then
      TimeReference:='UTC'
    else
      TimeReference:='Local';

    { Save Time reference setting. }
    vConfigurations.WriteString(PlotterINIsection,'TimeReference',TimeReference);

    { Change chart legend accordingly. }
    case TimeReference of
      'UTC': Chart1.AxisList[0].Title.Caption:='Sample time (UTC)';
      'Local': Chart1.AxisList[0].Title.Caption:='Sample time (Local)';
    end;

    {Update plot when Time reference has been changed}
    ReplotButtonClick(nil);
  end;//End of updating check
end;

//procedure TPlotterForm.VerticalSplitterMoved(Sender: TObject);
//begin
//  SplitterPosLeft:=VerticalSplitter.Left;
//
//  { Save splitter position for restarting }
//  vConfigurations.WriteString(PlotterINIsection,'SplitterPosLeft',IntToStr(SplitterPosLeft));
//end;
//

{!!! Maybe keep this !!!}
//procedure TPlotterForm.ThreeDayCheckBoxClick(Sender: TObject);
//begin
// ShellListView1Click(nil);
//end;

procedure TPlotterForm.TZLocationBoxChange(Sender: TObject);
begin
  if not TZChanging then begin
     TZChanging:=True;
     { Save the TZ selection. }
     PlotterTZLocation:=TZLocationBox.Text;
     Application.ProcessMessages;
     vConfigurations.WriteString(PlotterINIsection,'Local time zone',PlotterTZLocation);
     TZChanging:=False;
  end;
end;

procedure TPlotterForm.TZRegionBoxChange(Sender: TObject);
begin
 if not TZChanging then begin
   TZChanging:=True;

   { Get and save region }
   PlotterTZRegion:=TZRegionBox.Text;
   Application.ProcessMessages; //Wait for GUI to put screen text into variable.
   vConfigurations.WriteString(PlotterINIsection,'Local region',PlotterTZRegion); //Save TZ Region

   { Fill up timezone location names }
   FillTimezones();

   { Clear out selected time zone location because time zone region was just changed. }
   PlotterTZLocation:='';
   TZLocationBox.Text:=PlotterTZLocation;
   vConfigurations.WriteString(PlotterINIsection,'Local time zone',PlotterTZLocation);

   TZChanging:=False;
 end;
end;

procedure TPlotterForm.VoltageCheckBoxEditingDone(Sender: TObject);
begin

  if not UpdatingGUI then begin
    VoltageVisible:=VoltageCheckBox.Checked;
    {Save setting for future}
    vConfigurations.WriteBool(PlotterINIsection,'Voltage',VoltageVisible);

    VoltageText.Visible:=VoltageVisible;
    VoltageTextLabel.Visible:=VoltageVisible;
    Chart1.AxisList[3].Visible:=VoltageVisible;

    if VoltageVisible then
     VoltSeries.LinePen.Style:=psSolid
    else
     VoltSeries.LinePen.Style:=psClear;

  end;
end;

procedure TPlotterForm.WeekUpDownClick(Sender: TObject; Button: TUDBtnType);
begin
 if Button=btNext then
    inc(TimeOffset,604800)
 else
 dec(TimeOffset,604800);

TimeOffsetSpinEdit.Text:=IntToStr(TimeOffset);

ReplotButtonClick(nil);
end;

procedure TPlotterForm.ZeroPlotCheckBoxEditingDone(Sender: TObject);
begin
 ZeroPlot:=ZeroPlotCheckBox.Checked;
 vConfigurations.WriteBool(PlotterINIsection,'ZeroPlot',ZeroPlot);
end;

procedure TPlotterForm.AddFilesToList(FilePathName : String);
var
  FileSize : integer = 0; {Filesize, undetermined = 0}
  Row : integer = 1; {Start at row 1}
  FileName, FilePath, FileDate : string;
  sr : TSearchRec;
  PlotFileFilter : String;
begin
  FilePath := ExtractFilePath(FilePathName + DirectorySeparator);
  PlotFileFilter:=LeftStr(FilterComboBox.Text,5);
  PlotFilesStringGrid.RowCount:=1; {Reset file list}

  {Check if any files match criteria}
  if FindFirstUTF8(FilePath+PlotFileFilter,faAnyFile,sr)=0 then
    repeat
      {Get formatted file properties}
      FileName := ExtractFileName(sr.Name);
      FileSize := sr.Size;
      FileDate:=DateTimeToStr(FileDateToDateTime(sr.Time),FDateSettings);
      {Display found filename and timestamp}
      PlotFilesStringGrid.InsertRowWithValues(Row,[FileName, IntToStr(FileSize), FileDate ]);
      {Prepare for next file display}
      Row := Row + 1;
    until FindNextUTF8(sr)<>0;
  FindCloseUTF8(sr);

  {Initial sorting}
  PlotFilesStringGrid.SortColRow(true, 0);

end;

procedure TPlotterForm.FileSelect();
var
    InFilename,SelectedDateFile, PreviousDateFile,NextDateFile:String;
    File1: TextFile;
    Str: String;
    pieces: TStringList;

    MoonElevation: extended = 0.0;
    MoonAzimuth: extended = 0.0;

    UTCRecord, SelectedDate, PreviousDate, NextDate :TDateTime;
    PreviousDayValid : Boolean = False;
    SelectedDayValid : Boolean = False;
    NextDayValid : Boolean = False;
    MultiFileLoaded: Boolean = False;

    { 25 possible multi-plots are available with repeating line styles and cycling colors.}
    PlotLineStyles : array[0..24] of TFPPenStyle = (
                   psSolid, psDash, psDot, psDashDot, psDashDotDot,
                   psSolid, psDash, psDot, psDashDot, psDashDotDot,
                   psSolid, psDash, psDot, psDashDot, psDashDotDot,
                   psSolid, psDash, psDot, psDashDot, psDashDotDot,
                   psSolid, psDash, psDot, psDashDot, psDashDotDot
                   );
    PlotLineColors : array[0..24] of TColor = (
                   clRed, clGreen, clBlue, clFuchsia, clAqua,
                   clGreen, clBlue, clFuchsia, clAqua, clRed,
                   clBlue, clFuchsia, clAqua, clRed, clGreen,
                   clFuchsia, clAqua, clRed, clGreen, clBlue,
                   clAqua, clRed, clGreen, clBlue, clFuchsia
                   );

begin
  GetCustomizations();

  CurrentDirectory:=AddBackSlash(PlotDirectoryEdit.Text);

  pieces := TStringList.Create;

  { Cross hair enabled only for one chart }
  ChartToolset1DataPointCrosshairTool1.Enabled:=(PlotNumberGroup.ItemIndex=0);
  ReadingText.Visible:=ChartToolset1DataPointCrosshairTool1.Enabled;

  InFilename:= CurrentDirectory+PlotFilesStringGrid.Cells[0,PlotFilesStringGrid.Row];

  if ((not FileExists(InFilename)) or (InFilename='')) then
    exit;

  case FileSelectionMode of

    { In Single mode, new plot for each file clicked. }
    'Single': ClearAllButtonClick(nil);

    { TODO : what to do for ACCUMULATE file mode? }
    'Accumulate':;

    { In Multiple file mode, insert newly selected file into multi-plot list }
    'Multiple': begin
       {check if selected file does not exist in list}
       for MultiIndex:=0 to MultiFileStringGrid.RowCount-1 do begin
         if MultiFileStringGrid.Cells[1,MultiIndex]=ExtractFileName(InFilename) then begin
           MultiFileLoaded:=True;
           MultiChart[MultiIndex].Clear;
         end;
       end;

       if not MultiFileLoaded then begin

        {Get new row number, was initially set to 0 rows in form.create. }
        MultiIndex:=MultiFileStringGrid.RowCount;

        {Add the new row.}
        MultiFileStringGrid.RowCount:=MultiIndex+1;

        {Put the filename into the list.}
        MultiFileStringGrid.Cells[1,MultiIndex]:=ExtractFileName(InFilename);
        {Plot the new file:
          - in MPSASSeries if row=0.
          - in a newly created series if row>0}
        MultiChart[MultiIndex]:=TLineSeries.Create(self);
        MultiChart[MultiIndex].LinePen.Width:=2;
        MultiChart[MultiIndex].LinePen.Style:=PlotLineStyles[MultiIndex];
        MultiChart[MultiIndex].SeriesColor:=PlotLineColors[MultiIndex];
        MultiChart[MultiIndex].AxisIndexY:=1; //MPSAS axis
        Chart1.AddSeries(MultiChart[MultiIndex]);
       end;
    end;
   end;

  SelectedFilename:=InFilename;
  PlotterForm.Caption:='Plotter : '+SelectedFilename;

  {.txt files (from SQM Reader) }
  {.dat files}
  if ((ExtractFileExt(InFilename)= '.txt') or (ExtractFileExt(InFilename)= '.dat')) then begin
     PlotFile(InFilename);
  end;

  {.log should have date in filename}
  if (ExtractFileExt(InFilename)= '.log') then begin
      { Date should be encoded into filename, otherwise do not plot }
      try
        SelectedDate:=ScanDateTime('yyyymmdd',LazFileUtils.ExtractFileNameOnly(InFilename));
        SelectedDateFile:=InFilename;
        SelectedDayValid:=True;
      except
        on E: Exception do begin
          ShowMessage( 'Error: '+ E.ClassName  + #13#10 + E.Message + #13#10 + 'Error trying to get date from selected filename.');
          SelectedDayValid:=False;
        end;
      end;

      //Check day before and after for more plotting around selected date
      if ThreeDayCheckBox.Checked then begin
        try
          PreviousDate:=IncDay(SelectedDate,-1);
          PreviousDateFile:=CurrentDirectory+FormatDateTime('yyyymmdd',PreviousDate)+ExtractFileExt(InFilename);
          PreviousDayValid:=True;
        except
          on E: Exception do begin
            ShowMessage( 'Error: '+ E.ClassName  + #13#10 + E.Message + #13#10 + 'Error trying to get date from previous dated filename.');
            PreviousDayValid:=False;
          end;
        end;

        try
          NextDate:=IncDay(SelectedDate);
          NextDateFile:=CurrentDirectory+FormatDateTime('yyyymmdd',NextDate)+ExtractFileExt(InFilename);
          NextDayValid:=True;
        except
          on E: Exception do begin
            ShowMessage( 'Error: '+ E.ClassName  + #13#10 + E.Message + #13#10 + 'Error trying to get date from next dated filename.');
            NextDayValid:=False;
          end;
        end;
      end;

      try
        if PreviousDayValid then PlotFile(PreviousDateFile);
        if SelectedDayValid then PlotFile(SelectedDateFile);
        if NextDayValid     then PlotFile(NextDateFile);
      except
        on E: Exception do
          ShowMessage( 'Error: '+ E.ClassName  + #13#10 + E.Message + #13#10 + 'Error trying to plot file(s).');
      end;

  end;

  if Assigned(pieces) then FreeAndNil(pieces);

//.............................................................................
//.............................................................................

 exit;
(*
  {$I+}
  try
    Reset(File1);

    repeat
      // Read one line at a time from the file.
      Readln(File1, Str);
      // Get location data from header.
      if AnsiStartsStr('# Position',Str) then begin
          //Prepare for parsing.
          pieces.Delimiter := ',';
          pieces.StrictDelimiter := True; //Do not parse spaces.
          //Remove comment from beginning of line.
          pieces.DelimitedText := AnsiRightStr(Str,length(Str) - RPos(':',Str));
          if (pieces.Count<2) then begin
              MessageDlg('Error', 'No location data found in header.', mtError, [mbOK],0);
              MessageDlg('Location Error',
                'Error: No location data found in header.',
                mtError,
                [mbOK],0);
              //LatitudeDisplay.Text:='nul';
              //LongitudeDisplay.Text:='nul';
              //break;
          end else begin
             //MyLatitude:=StrToFloat(pieces.Strings[0]);
             //if ((MyLatitude<-90.0) or (MyLatitude>90.0)) then begin
             //    writeln('Error: Latitude ',MyLatitude,' out of range');
             //    break;
             //  end;
             //MyLongitude:=StrToFloat(pieces.Strings[1]);
             //if ((MyLongitude<-180.0) or (MyLongitude>180.0)) then begin
             //    writeln('Error: Longitude ',MyLongitude,' out of range');
             //    break;
             //  end;
             // LatitudeDisplay.Text:=Format('%.3f',[MyLatitude]);
             // LongitudeDisplay.Text:=Format('%.3f',[MyLongitude]);
            end;
        end;

      //Get and modify record header string
      //if AnsiStartsStr('# YYYY-MM-DDTHH:mm:ss.fff;',Str) then
      //  Writeln('UTC '+AnsiRightStr(Str,length(Str) - 2)+';MoonPhaseDeg;MoonElevDeg;MoonIllum%');

      //Ignore comment lines which have # as first character.
      if not AnsiStartsStr('#',Str) then begin
          //Separate the fields of the record.
          pieces.Delimiter := ';';
          pieces.DelimitedText := Str;

          //Make sure there are enough fields
          if ((pieces.Count<5) or (pieces.Count>6)) then begin
              MessageDlg('Error', 'Incorrect number of fields in record.', mtError, [mbOK],0);
              break;
            end
          else
          begin

          //parse the fields, and convert as necessary.
          //Convert UTC string 'YYYY-MM-DDTHH:mm:ss.fff' into TDateTime
          UTCRecord:=ScanDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz',pieces.Strings[0]);

          //Calculate Moon position
          //Change sign for Moon calculations
          Moon_Position_Horizontal(
                                   StrToDateTime(DateTimeToStr(UTCRecord)),
                                   -1.0*MyLongitude,
                                   MyLatitude,
                                   MoonElevation,
                                   MoonAzimuth);

          end;//End of checking number of fields in record.

        end;
    until(EOF(File1)); // EOF(End Of File) The the program will keep reading new lines until there is none.
    CloseFile(File1);
  except
    on E: EInOutError do begin
     MessageDlg('Error', 'File handling error occurred. Details: '+E.ClassName+'/'+E.Message, mtError, [mbOK],0);
    end;
  end;
*)
end;

function FileSizeFormat(bytes:Double):string;
begin

  // below 1024 bytes
  if bytes < 1024 then
    Result:=floattostr(bytes) + 'b'

  // kilobytes; 1kb = 1024bytes
  else if (bytes >= 1024) and (bytes < 1024**2) then
    Result:=FormatFloat('0.00', bytes/1024)+'kb'

  // megabytes; 1mb = 1024*1024bytes
  else if (bytes >= 1024**2) and (bytes < 1024**3) then
    Result:=FormatFloat('0.00', bytes/1024/1024)+'Mb'

  // gigabytes; 1gb = 1024*1024*1024bytes
  else if (bytes >= 1024**3) and (bytes < 1024**4) then
    Result:=FormatFloat('0.00', bytes/1024/1024/1024)+'Gb'

  // for everything above, we show it in terrabytes
  else
    Result:=FormatFloat('0.00', bytes/1024/1024/1024/1024)+'Tb';

end;

function AddBackSlash(Instring:String):String;
begin
  Result:=Instring+DirectorySeparator;
end;

function DialogCentered(const aCaption: String;
                  const aMsg: string;
                  DlgType: TMsgDlgType;
                  Buttons: TMsgDlgButtons;
                  HelpCtx: longint;
                  Form: TForm
                  ): TModalResult;
var f: TForm;
begin
  Result := 0;
  f:=CreateMessageDialog(aMsg, DlgType, Buttons);
  try
    try
      f.Caption:=aCaption;
      f.Position:=poDesigned;
      f.Left:=PlotterForm.Left+(PlotterForm.Width div 2)-f.Width div 2;
      f.Top:=PlotterForm.Top + (PlotterForm.Height div 2)-f.Height div 2;
      Result:=f.ShowModal;
    except
      ShowMessage('Plotter: DialogCentered exception');
    end;
  finally
    f.Free;
  end;
end;
Procedure TPlotterForm.FillTimezones();
begin
  if (FileExists(appsettings.TZDirectory+PlotterTZRegion) and (length(PlotterTZRegion)>0))then begin
     ptzRegion.Destroy;
     ptzRegion := TPascalTZ.Create();
     try
       ptzRegion.ParseDatabaseFromFile(appsettings.TZDirectory+PlotterTZRegion);
     except
       ShowMessage(Format('Failed getting zones from %s',[PlotterTZRegion]));
     end;
     ptzRegion.GetTimeZoneNames(AZones,true);  //only geo name = true, does not show short names
     TZLocationBox.Items.Clear;
     TZLocationBox.Items.AddStrings(AZones);

  end;
end;

initialization
  {$I plotter.lrs}

end.

