unit GraphManager2;

  interface
    uses
      AdsData       (* tAdsStringField; also used by AdsFunc *),
      AdsFunc       (* used by AdsTable *),
      AdsTable      (* tAdsQuery, tAdsTable *),
      AdvGrid       (* TMS *),
      AdvObj        (* TMS *),
      AdvUtil       (* TMS *),
      BaseGrid      (* TMS *),
      Buttons       (* tBitBtn *),
      Classes       (* tStringList *),
      ComCtrls      (* tPageControl, tTabSheet *),
      Controls      (**),
      DB            (* tDataSource, tDataSet, tBooleanField, tIntegerField,
                         tMemoField, tStringField; also used by AdsTable *),
      DBAdvGrid     (* tDBAdvGrid *),
      DBCtrls       (* tDBMemo, tDBNavigator *),
      ExtCtrls      (* tShape *),
      Forms         (* tForm *),
      GenericGraph3 (* tfmGenericGraphForm *),
      Graphics      (* tColor; also used by TMS *),
      Grids         (* used by TMS *),
      HC3DM         (* tAxisType, Global, tUIDForSet *),
      MyConsts      (* nullstring, UsualFormColor *),
      StdCtrls      (* tButton, tCheckBox, tGroupBox, tLabel, tListBox, tMemo *),
      Windows       (* tRect *);

    type
      tGenericGraph = class(tObject)
        private
          TheBackgroundColor: tColor;
          TheYAxisLeft: tAxisType;
          TheYAxisRight: tAxisType;
        public
          TheAutoStart: boolean;
          FormIsOpen: boolean;
          LastRedrawn: tDateTime;
          TheForm: tfmGenericGraphForm;
          TheUID: integer;
          TheCaption: string;
          SensorUIDs: set of tUIDForSet;
          constructor Create(const AutoStart: boolean;
                             const BackgroundColor: tColor;
                             const Caption: string;
                             const UID: integer;
                             const YAxisLeft, YAxisRight: tAxisType);
          destructor  Destroy; override;
        end (* tGenericGraph *);

    type
      tGraphList = class(tStringList)
        public
          destructor Destroy; override;
        end (* tGraphList *);

    type
      tfmGraphManager = class(tForm)
          pgctlGraphManager: tPageControl;
            tabGraphs: tTabSheet;
              qGraphs: tAdsQuery;
                qGraphsAutoStart:    tAdsStringField;
                qGraphsCaption:      tAdsStringField;
                qGraphsCelsiusScale: tAdsStringField;
                qGraphsTitleForMenu: tAdsStringField;
                qGraphsFrequent:    tBooleanField;
                qGraphsIndoor:      tBooleanField;
                qGraphsBackgroundColor: tIntegerField;
                qGraphsUID:             tIntegerField;
                qGraphsYAxisLeft:       tIntegerField;
                qGraphsYAxisRight:      tIntegerField;
                qGraphsDescription: tMemoField;
                dataGraphs: tDataSource;
                  dbadvgridGraphs: tDBAdvGrid;
                  dbmemoGraphDescription: tDBMemo;
                  qDeleteGraph: tAdsQuery;
              qGraphSeries: tAdsQuery;
                qGraphSeriesAxis:               tAdsStringField;
                qGraphSeriesBoardName:          tAdsStringField;
                qGraphSeriesFieldName:          tAdsStringField;
                qGraphSeriesLegend:             tAdsStringField;
                qGraphSeriesObservedString:     tAdsStringField;
                qGraphSeriesSeriesKindString:   tAdsStringField;
                qGraphSeriesTableName:          tAdsStringField;
                qGraphSeriesWhatMeasuredString: tAdsStringField;
                qGraphSeriesColor:        tIntegerField;
                qGraphSeriesObservedUID:  tIntegerField;
                qGraphSeriesPlotOrder:    tIntegerField;
                qGraphSeriesSensorUID:    tIntegerField;
                qGraphSeriesSeriesKind:   tIntegerField;
                qGraphSeriesTableUID:     tIntegerField;
                qGraphSeriesWhatMeasured: tIntegerField;
                qGraphSeriesQuerySchema: tMemoField;
                qGraphSeriesRangeQuery:  tMemoField;
              btnNewGraph:      tButton;
              btnShowThisGraph: tButton;
              shapeDeleteGraph: tShape;
                btnDeleteThisGraph: tButton;

            tabDevelopment: tTabSheet;
              btnSaveDevelopment: tButton;
              dbmemoQuerySchema: tDBMemo;
              grpAnnotateLog: tGroupBox;
                btnNewLine: tButton;
                edAnnotateLog: tEdit;
              memoDevelopment: tMemo;
              shapeClearAlert: tShape;
                btnClearDevelopment: tButton;

            tabUtility: tTabSheet;
              qLight: tAdsQuery;
              tbLight: tAdsTable;
              btnFixLightTable: tButton;

          procedure btnClearDevelopmentClick (Sender: tObject);
          procedure btnDeleteThisGraphClick  (Sender: tObject);
          procedure btnFixLightTableClick    (Sender: tObject);
          procedure btnNewGraphClick         (Sender: tObject);
          procedure btnNewLineClick          (Sender: tObject);
          procedure btnSaveDevelopmentClick  (Sender: tObject);
          procedure btnShowThisGraphClick    (Sender: tObject);
          procedure dbadvgridGraphsDblClick  (Sender: tObject);
          procedure FormDblClick             (Sender: tObject);
          procedure FormShow                 (Sender: tObject);

        private
          DebugDumpGraphTable: boolean;
          DebugSensorAssignments: boolean;
          DebugShowGraph: boolean;
          DebugUpdateGraphs: boolean;
          function GraphIndex(const GraphUID: integer): integer;
        public
          procedure DevelopmentNote(const S: string);
          procedure DumpGraphTable(const S: string);
          procedure Initialize;
          procedure GraphClosed(const GraphUID: integer);
          procedure KillAndReviveGraph(const GraphUID: integer;
                                       const RangeBegin, RangeEnd: tDate);
          procedure ReplotOpenGraphs;
          procedure SaveLog;
          procedure ShowGraph(const GraphUID: integer; TheMessage: string = nullstring);
          procedure UpdateGraphs(const SensorUID: integer);
        end (* tfmGraphManager *);

    var
      fmGraphManager: tfmGraphManager;
      GraphList: tGraphList;
      MenuTitles: tStringList;

  implementation
    uses
      AddOrEditGraph         (* fmAddOrEditGraph *),
      DateUtils              (* SecondsBetween *),
      DBAdvGridUtils         (* SetUpDBAdvGridReadOnly *),
      Dialogs                (* ShowMessage *),
      GraphSeries3           (* InitializeGraphSeries *),
      HC4Main                (* fmHC4Main *),
      HC3Utilities           (* DateTest, INILoadBoolean, LastUIDOfOld,
                                  SaveListBox, TrimInternalWhiteSpace *),
      ObservedAndWM          (* tObserved, rrftBoolean, rrftBooleanIntent,
                                  tTableUsed, tWhatMeasured *),
      RRF_StringAndTimeUtils (* UTCToLocal *),
      SysUtils               (* Format, FormatDateTime, Now *),
      TableManager           (* TableInfo *);
      {$R *.dfm}

{********* infrastructure *****************************************************}
    procedure tfmGraphManager.DevelopmentNote(const S: string);
      (* local log *)
    
      begin (* tfmGraphManager.DevelopmentNote *)
        with memoDevelopment do
          Lines.Add(S)
      end   (* tfmGraphManager.DevelopmentNote *);

    procedure tfmGraphManager.FormDblClick(Sender: tObject);
      (* show master form *)

      begin (* tfmGraphManager.FormDblClick *)
        fmHC4Main.Show
      end   (* tfmGraphManager.FormDblClick *);

    procedure tfmGraphManager.FormShow(Sender: TObject);
      (* not in IDE *)

      begin (* tfmGraphManager.FormShow *)
        self.Color := UsualFormColor;
        SetUpDBAdvGridReadOnly(dbadvgridGraphs);
          MarlettCheckField('AutoStart', 'auto start')
      end   (* tfmGraphManager.FormShow *);

    function tfmGraphManager.GraphIndex(const GraphUID: integer): integer;
      (* find index in GraphList of this GraphUID *)

      var
        I: integer;

      begin (* tfmGraphManager.GraphIndex *)
        Result := -1;
        with GraphList do
          for I := 1 to Count do
            with tGenericGraph(GraphList.Objects[I-1]) do
              if TheUID = GraphUID then
                begin (* this is it *)
                  Result := I - 1;
                  break;
                end   (* this is it *)
      end   (* tfmGraphManager.GraphIndex *);

    procedure tfmGraphManager.Initialize;
      (* needed even if form not shown *)

      var
        AutoStart: boolean;
        BackgroundColor: tColor;
        UID: integer;
        Caption, TitleForMenu: string;
        YAxisLeft, YAxisRight: tAxisType;

      begin (* tfmGraphManager.Initialize *)
        DebugDumpGraphTable    := INILoadBoolean('Graph Manager', 'DumpGraphTable');
        DebugSensorAssignments := INILoadBoolean(                 'DebugSensorAssignments');
        DebugShowGraph         := INILoadBoolean(                 'DebugShowGraph');
        DebugUpdateGraphs      := INILoadBoolean(                 'DebugUpdateGraphs');
        InitializeGraphSeries;
        fmAddOrEditGraph.Initialize;
        GraphList := tGraphList.Create;
        MenuTitles := tStringList.Create;
        with qGraphs do
          begin (* qGraphs *)
            Open;
            while not EOF do
              begin (* one record *)
                AutoStart       :=           FieldByName('AutoStart'      ).AsBoolean;
                BackgroundColor := tColor(   FieldByName('BackgroundColor').AsInteger);
                UID             :=           FieldByName('UID'            ).AsInteger;
                Caption         :=           FieldByName('Caption'        ).AsString;
                TitleForMenu    :=           FieldByName('TitleForMenu'   ).AsString;
                YAxisLeft       := tAxisType(FieldByName('YAxisLeft'      ).AsInteger);
                YAxisRight      := tAxisType(FieldByName('YAxisRight'     ).AsInteger);
                GraphList.AddObject(TitleForMenu, tGenericGraph.Create(AutoStart,
                                                                       BackgroundColor,
                                                                       Caption,
                                                                       UID,
                                                                       YAxisLeft,
                                                                       YAxisRight));
                MenuTitles.Add(TitleForMenu);
                Next
              end   (* one record *)
          end   (* qGraphs *);
        fmHC4Main.RebuildGraphMenu;
        pgctlGraphManager.ActivePage := tabGraphs
      end   (* tfmGraphManager.Initialize *);

    procedure tfmGraphManager.SaveLog;
      (* service for MC3Main *)

      begin (* tfmGraphManager.SaveLog *)
        btnSaveDevelopmentClick(nil)
      end   (* tfmGraphManager.SaveLog *);

{ tGraphList }

    destructor tGraphList.Destroy;
      (* reclaim storage *)

      var
        I: integer;

      begin (* tGraphList.Destroy *)
        for I := Count downto 1 do
          tGenericGraph(Objects[I-1]).Free;

        inherited Destroy
      end   (* tGraphList.Destroy *);

{ tGenericGraph }

    constructor tGenericGraph.Create(const AutoStart: boolean;
                                     const BackgroundColor: tColor;
                                     const Caption: string;
                                     const UID: integer;
                                     const YAxisLeft, YAxisRight: tAxisType);
      (* constructor *)

      procedure ScanSeries;
        (* create range schemata, and accumulate SensorUIDs  When the dates
         are known, the schemata will be used
              without _old             (recent dates)
              as is                    (old dates)
              without _old union as is (bridging dates)
        *)

        var
          SensorUID: integer;
          ObservedUID: tObserved;
          Axis, FieldName, ObservedTest, RangeSQL, TableName: string;
          TU: tTableUsed;
          WM: tWhatMeasured2;

        begin (* ScanSeries *)
          with fmGraphManager.qGraphSeries do
            begin (* qGraphSeries *)
              Close;
              ParamByName('GraphUID').AsInteger := UID;
              Open;
              while not EOF do
                begin (* one series *)
                  Axis        :=                FieldByName('Axis'        ).AsString;
                  FieldName   :=                FieldByName('FieldName'   ).AsString;
                  SensorUID   :=                FieldByName('SensorUID'   ).AsInteger;
                  TableName   :=                FieldByName('TableName'   ).AsString;
                  TU          :=     tTableUsed(FieldByName('TableUID'    ).AsInteger);
                  ObservedUID :=      tObserved(FieldByName('ObservedUID' ).AsInteger);
                  WM          := tWhatMeasured2(FieldByName('WhatMeasured').AsInteger);

                  if (ObservedUID = obImplicit) or not TableInfo[TU].UsesObserved then
                    ObservedTest := nullstring
                  else
                    ObservedTest := Format(' and ObservedUID = %d', [ord(ObservedUID)]);

                  if MeasurementInfo2[WM].FieldType = rrftBoolean then
                    RangeSQL := nullstring
                  else if MeasurementInfo2[WM].FieldType = rrftBooleanIntent then
                    RangeSQL := nullstring
                  else if ((Axis[1] = 'L') and (TheYAxisLeft  <> atStandard)) or
                          ((Axis[1] = 'R') and (TheYAxisRight <> atStandard)) then
                    RangeSQL := Format(' select               ' +
                                       '   count(*) as NData, ' +
                                       '   min(%s) as TheMin, ' +
                                       '   max(%s) as TheMax  ' +
                                       ' from                 ' +
                                           TableName + '_old  ' +
                                       ' where                ' +
                                           DateTest + ObservedTest,
                                       [FieldName, FieldName])
                  else
                    RangeSQL := nullstring;

                  Edit;
                    FieldByName('RangeQuery').AsString := RangeSQL;
                  Post;
                  SensorUIDs := SensorUIDs + [tUIDForSet(SensorUID)];
                  Next
                end   (* one series *);
              Close
            end   (* qGraphSeries *)
        end   (* ScanSeries *);

      begin (* tGenericGraph.Create *)
        inherited Create;
        LastRedrawn        := 0;
        TheAutoStart       := AutoStart;
        TheBackgroundColor := BackgroundColor;
        TheCaption         := Caption;
        TheUID             := UID;
        TheYAxisLeft       := YAxisLeft;
        TheYAxisRight      := YAxisRight;

        TheForm    := nil;
        FormIsOpen := false;
        SensorUIDs := [];

        ScanSeries
      end   (* tGenericGraph.Create *);

    destructor tGenericGraph.Destroy;
      (* reclaim storage *)
    
      begin (* tGenericGraph.Destroy *)
        if assigned(TheForm) then
          TheForm.Free;
        inherited Destroy
      end   (* tGenericGraph.Destroy *);

{********* end infrastructure *************************************************}
{********* graphs tab *********************************************************}
    procedure tfmGraphManager.btnDeleteThisGraphClick(Sender: tObject);
      (* not if showing.  also, need to delete references, queries, etc. *)
    
      var
        Index, UIDToDelete: integer;

      begin (* tfmGraphManager.btnDeleteThisGraphClick *)
        UIDToDelete := qGraphs.FieldByName('UID').AsInteger;
        Index := GraphIndex(UIDToDelete);

        with tGenericGraph(GraphList.Objects[Index]) do
          if not FormIsOpen then
            begin (* delete it *)
              qDeleteGraph.ExecSQL;
              GraphList.Delete(Index);
              qGraphs.Close;
              qGraphs.Open;
            end   (* delete it *)
      end   (* tfmGraphManager.btnDeleteThisGraphClick *);

    procedure tfmGraphManager.btnNewGraphClick(Sender: tObject);
      (* other form does all the work *)

      begin (* tfmGraphManager.btnNewGraphClick *)
        ShowMessage('New graph will not be visible until after restart of application.');
        fmAddOrEditGraph.NewGraph
      end   (* tfmGraphManager.btnNewGraphClick *);

    procedure tfmGraphManager.btnShowThisGraphClick(Sender: tObject);
      (* other code does all the work *)
    
      begin (* tfmGraphManager.btnShowThisGraphClick *)
        ShowGraph(qGraphs.FieldByName('UID').AsInteger, ' per ShowThisGraph button')
      end   (* tfmGraphManager.btnShowThisGraphClick *);

    procedure tfmGraphManager.GraphClosed(const GraphUID: integer);
      (* update status *)

      begin (* tfmGraphManager.GraphClosed *)
        with tGenericGraph(GraphList.Objects[GraphIndex(GraphUID)]) do
          FormIsOpen := false
      end   (* tfmGraphManager.GraphClosed *);

    procedure tfmGraphManager.dbadvgridGraphsDblClick(Sender: tObject);
      (* edit this graph.  *)

      var
        GraphUID: integer;

      begin (* tfmGraphManager.dbadvgridGraphsDblClick *)
        with qGraphs do
          begin (* qGraphs *)
            GraphUID    := FieldByName('UID'         ).AsInteger;
            fmAddOrEditGraph.EditGraph(GraphUID);
            Close;
            Open;
            if not Locate('UID', GraphUID, []) then
              (* ignore it *)
          end   (* qGraphs *)
      end   (* tfmGraphManager.dbadvgridGraphsDblClick *);

    procedure tfmGraphManager.KillAndReviveGraph(const GraphUID: integer;
                                                 const RangeBegin, RangeEnd: tDate);
      (* called from GenericGraph after date range changed *)
    
      begin (* tfmGraphManager.KillAndReviveGraph *)
        with tGenericGraph(GraphList.Objects[GraphIndex(GraphUID)]) do
          begin (* the generic graph *)
            TheForm.Free;
            TheForm := tfmGenericGraphForm.Create(nil);
            TheForm.Initialize(GraphUID);
            TheForm.ChangeDateRange(RangeBegin, RangeEnd);
            TheForm.Plot;
            FormIsOpen := true
          end   (* the generic graph *)
      end   (* tfmGraphManager.KillAndReviveGraph *);

    procedure tfmGraphManager.ReplotOpenGraphs;
      (* service for Dashboard, at end of dump *)

      var
        I: integer;

      begin (* tfmGraphManager.ReplotOpenGraphs *)
        with GraphList do
          for I := 1 to Count do
            with tGenericGraph(GraphList.Objects[I-1]) do
              if assigned(TheForm) then
                if FormIsOpen then
                  TheForm.Plot
      end   (* tfmGraphManager.ReplotOpenGraphs *);

    procedure tfmGraphManager.ShowGraph(const GraphUID: integer; TheMessage: string);
      (* can't seem to get new data without killing & recreating form *)

      begin (* tfmGraphManager.ShowGraph *)
        if DebugShowGraph then
          begin (* verbose *)
            DevelopmentNote(TheMessage);
            DumpGraphTable(Format('    entering GM.ShowGraph(%d)', [GraphUID]));
          end   (* verbose *);

        with tGenericGraph(GraphList.Objects[GraphIndex(GraphUID)]) do
          begin (* found tGenericGraph object *)
            if not assigned(TheForm) then
              begin (* create it *)
                TheForm := tfmGenericGraphForm.Create(nil);
                TheForm.Initialize(GraphUID)
              end   (* create it *);

            TheForm.Plot;
            FormIsOpen := true
          end   (* found tGenericGraph object *);

        if DebugShowGraph then
          begin (* verbose *)
            DumpGraphTable(Format('    leaving GM.ShowGraph(%d)', [GraphUID]));
            DevelopmentNote(nullstring)
          end   (* verbose *)
      end   (* tfmGraphManager.ShowGraph *);

    procedure tfmGraphManager.UpdateGraphs(const SensorUID: integer);
      (* give graphs that use this sensor a chance to redraw *)
       { TODO : might make Plot contingent on Now being within date range of that graph }

      var
        TheGraph: tGenericGraph;
        I: integer;

      function ThisGraphEligible: boolean;
        (* most graphs are irrelevant *)

        procedure ShowFormSensors;
          (* debugging *)

          var
            J: integer;
            FormSensors: string;

          begin (* ShowFormSensors *)
            FormSensors := Format('  %s graph sensors',
                                  [GraphList.Strings[I-1]]);
            for J := 1 to 255 do
              if J in tGenericGraph(GraphList.Objects[I-1]).SensorUIDs then
                FormSensors := Format('%s #%d', [FormSensors, J]);
            // DevelopmentNote(FormSensors)
          end   (* ShowFormSensors *);

        begin (* ThisGraphEligible *)
          Result := false;
          with TheGraph do
            if assigned(TheForm) and FormIsOpen then
              begin (* check this graph *)
                if DebugSensorAssignments then
                  ShowFormSensors;
                Result := (SensorUID in SensorUIDs)
              end   (* check this graph *)
        end   (* ThisGraphEligible *);

      begin (* tfmGraphManager.UpdateGraphs *)
        with GraphList do
          for I := 1 to Count do
            begin (* one graph *)
              TheGraph := tGenericGraph(GraphList.Objects[I-1]);
              if ThisGraphEligible then
                with TheGraph do
                  if SecondsBetween(Now, LastRedrawn) > Global.DecentGraphInterval then
                    begin (* shown *)
                      if DebugUpdateGraphs then
                        DevelopmentNote(Format('    %s plotting %s graph (UID %d, index %d) for %s (#%d) ************',
                                               [FormatDateTime('hh:nn:ss', Now),
                                                GraphList.Strings[I-1],
                                                TheUID, I-1,
                                                Global.LatestData.SensorTypeName,
                                                SensorUID]));
                      ShowGraph(TheUID, Format('   for sensor #%d', [SensorUID]));

                      LastRedrawn := Now
                    end   (* shown *)
                 else if DebugUpdateGraphs then
                   DevelopmentNote(Format('    %s too soon to plot %s graph for %s',
                                          [FormatDateTime('hh:nn:ss', Now),
                                           GraphList.Strings[I-1],
                                           Global.LatestData.SensorTypeName]))
            end   (* one graph *);

        if DebugUpdateGraphs then
          begin (* verbosity *)
            DevelopmentNote(Format('leaving GM.UpdateGraphs(%d)', [SensorUID]));
            DevelopmentNote(nullstring)
          end   (* verbosity *)
      end   (* tfmGraphManager.UpdateGraphs *);

{********* end graphs tab *****************************************************}
{********* development tab ****************************************************}
    procedure tfmGraphManager.btnClearDevelopmentClick(Sender: tObject);
      (* cliche *)

      begin (* tfmGraphManager.btnClearDevelopmentClick *)
        memoDevelopment.Clear
      end   (* tfmGraphManager.btnClearDevelopmentClick *);

    procedure tfmGraphManager.btnNewLineClick(Sender: tObject);
      (* insert item just after current one *)

      begin (* tfmGraphManager.btnNewLineClick *)
        memoDevelopment.Lines.Add(edAnnotateLog.Text)
      end   (* tfmGraphManager.btnNewLineClick *);

    procedure tfmGraphManager.btnSaveDevelopmentClick(Sender: tObject);
      (* cliche *)

      begin (* tfmGraphManager.btnSaveDevelopmentClick *)
        // SaveListBox(memoDevelopment.Lines, 'graph development', 'save to file')
      end   (* tfmGraphManager.btnSaveDevelopmentClick *);

    procedure tfmGraphManager.DumpGraphTable(const S: string);
      (* debugging aid *)
    
      var
        I: integer;

      begin (* tfmGraphManager.DumpGraphTable *)
        DevelopmentNote(S);
        if DebugDumpGraphTable then
          begin (* verbose *)
            DevelopmentNote('      index    in table      in form');
            with GraphList do
              for I := 1 to Count do
                with tGenericGraph(GraphList.Objects[I-1]) do
                  if assigned(TheForm) then
                    DevelopmentNote(Format('        %d     %s (#%d)      %s (#%d)',
                                                [I-1,  TheCaption, TheUID,
                                                       TheForm.Caption, TheForm.TheGraphUID]))
          end   (* verbose *)
      end   (* tfmGraphManager.DumpGraphTable *);

{********* end development tab ************************************************}
{********* utility tab ********************************************************}
    procedure tfmGraphManager.btnFixLightTableClick(Sender: tObject);
      (* eliminate ramps *)

      var
        NewWRUTC, OldWRUTC, WRUTC: tDateTime;
        NewUID: integer;
        Light, OldLight: real;

      begin (* tfmGraphManager.btnFixLightTableClick *)
        OldWRUTC := 0;
        OldLight := 0;  // suppress spurious warning
        NewUID := LastUIDOf('Light') + 1;
        tbLight.Open;

        with qLight do
          begin (* qLight *)
            Open;
              while not EOF do
                begin (* one record *)
                  WRUTC := FieldByName('WhenRecordedUTC').AsDateTime;
                  Light := FieldByName('Light'          ).AsFloat;

                  if OldWRUTC <> 0 then
                    if OldLight = 0 then
                      if Light > 0 then
                        if SecondsBetween(WRUTC, OldWRUTC) > 10 then
                          with tbLight do
                            begin (* break ramp *)
                              NewWRUTC := IncSecond(WRUTC, -1);
                              Insert;
                                FieldByName('Light'          ).AsFloat    := 0;
                                FieldByName('ObservedUID'    ).AsInteger  := 13;
                                FieldByName('UID'            ).AsInteger  := NewUID;
                                FieldByName('WhenRecorded'   ).AsDateTime := UTCToLocal(NewWRUTC);
                                FieldByName('WhenRecordedUTC').AsDateTime := NewWRUTC;
                              Post;
                              inc(NewUID)
                            end   (* break ramp *);
                  OldWRUTC := WRUTC;
                  OldLight := Light;
                  Next
                end   (* one record *);

            Close
          end   (* qLight *);
        tbLight.Close
      end   (* tfmGraphManager.btnFixLightTableClick *);

end.
