unit HC4Main;
  interface
    uses
      AdsData  (* tAdsStringField *),
      AdsFunc  (* used by AdsTable *),
      AdsTable (* tAdsQuery *),
      Classes  (* tNotifyEvent *),
      Controls (* tCreateParams, tDate, Handle, mrCancel, mrYes *),
      DB       (* used by AdsTable *),
      ExtCtrls (* tTimer *),
      Forms    (* Application, tCloseAction, tForm, Screen *),
      Menus    (* tMainMenu, tMenuItem *),
      Messages (* tMessage *),
      MyUtils  (* AskYesNo2, AskYesNoCancel, UM_EnsureRestored *),
      StdCtrls (* tLabel *),
      Windows  (* IsIconic, SetForegroundWindow, SW_SHOWNORMAL *);

    type
      tfmHC4Main = class(tForm)
          qFilterHours:   tAdsQuery;
          qGraphsForMenu: tAdsQuery;
          lbBoardQuiet:     tLabel;
          lbMotionDetected: tLabel;
          lbReboot:         tLabel;
          lbTimeSinceInput: tLabel;
          menuMain: tMainMenu;
            menuArrange:   tMenuItem;
            menuDashboard: tMenuItem;
            menuGraphs:    tMenuItem;
            menuSensors:   tMenuItem;
              menuSensorIndoor:     tMenuItem;
              menuSensorsOutdoor:   tMenuItem;
              menuSensorSeparator1: tMenuItem;
              menuSensorSimulated:  tMenuItem;
              menuSensorSeparator2: tMenuItem;
              menuBoardManager:     tMenuItem;
              menuSensorManager:    tMenuItem;
            menuTweaks: tMenuItem;
              menuTweakWarnOff:   tMenuItem;
              menuTweakPhotocell: tMenuItem;
            menuUtilities: tMenuItem;
              menuUtilitiesPrinter:      tMenuItem;
              menuUtilitiesTableManager: tMenuItem;
              menuUtilitiesDepth:        tMenuItem;
              menuUtilitiesWeather:      tMenuItem;
              menuUtilitiesSeparator:    tMenuItem;
                menuUtilitiesFurnaceFilter:      tMenuItem;
                menuUtilitiesResetFurnaceFilter: tMenuItem;
            menuView: tMenuItem;
              menuVieweFrames:   tMenuItem;
              menuViewExtremes:  tMenuItem;
              menuViewImageList: tMenuItem;
              menuViewLog:       tMenuItem;
              menuViewNotes:     tMenuItem;
              menuViewRelays:    tMenuItem;
              menuViewSummary:   tMenuItem;
          timerLastInput: tTimer;

          procedure FormShow                            (Sender: tObject);
          procedure GraphMenuClick                      (Sender: tObject);
          procedure menuArrangeClick                    (Sender: tObject);
          procedure menuTweakWarnOffClick               (Sender: tObject);
          procedure menuUtilitiesDepthClick             (Sender: tObject);
          procedure menuUtilitiesFurnaceFilterClick     (Sender: tObject);
          procedure menuUtilitiesResetFurnaceFilterClick(Sender: tObject);
          procedure RunGraphManager                     (Sender: tObject);
          procedure ShowGroupedOutsideGraph             (Sender: tObject);
          procedure ShowTheForm                         (Sender: tObject);
          procedure timerLastInputTimer                 (Sender: tObject);

          procedure FormClose(Sender: tObject; var Action: tCloseAction);
        private
          FreezeBeeped: boolean;
          DepthUpdateAppLoc: string;
          procedure UMEnsureRestored(var Msg: tMessage); message UM_EnsureRestored;
        protected
          procedure CreateParams(var Params: tCreateParams); override;
        public
          procedure BoardQuiet(const BoardName: string; const Since: tDateTime);
          procedure BoardRebooted(const TheMessage: string);
          procedure MenuEnabled(const WantEnabled: boolean);
          procedure MotionDetected(const Observed: string;
                                   const LocalTime: tDateTime);
          procedure RebuildGraphMenu;
        end (* tfmHC4Main *);

    var
      fmHC4Main: tfmHC4Main;

  implementation
    uses
      BoardManager           (* fmBoardManager *),
      Contnrs                (* tObjectList *),
      Dashboard              (* fmDashboard *),
      DateUtils              (* Now, MilliSecondsBetween, MinutesBetween,
                                  SecondsBetween, Today *),
      Dialogs                (* ShowMessage, ShowMessageFmt *),
      eFrames                (* fmeFrames *),
      Extremes2              (* fmExtremes2 *),
      FixPhotocell           (* fmFixPhotocell *),
      GraphAnnualTemps       (* fmAnnualTemps *),
      Graphics               (* clRed, clWindowText *),
      GraphManager2          (* fmGraphManager *),
      GraphOutsideGrouped    (* fmGroupedOutsideGraph *),
      GraphWind              (* fmGraphWind *),
      HC3DM                  (* Global, gsUnconnected, UsualFormColor *),
      HC3Utilities           (* LeftMonitorRight, INILoadBoolean, INILoadDateTime,
                                  INILoadString, INISaveDateTime,
                                  LeftMonitorScale, ms175 *),
      IndoorInstruments      (* fmIndoorSensors *),
      MyConsts               (* AMPERSAND, HYPHEN, nullstring, UsualFormColor *),
      LabelAdjuster          (* InitializeLabelAdjuster, TheLabelAdjuster *),
      LogManager             (* fmLogManager *),
      NoteManager            (* fmNoteManager *),
      ObservedAndWM          (* obAC, obFurnace *),
      OutDoorInstruments     (* fmOutdoorInstruments *),
      PickDateRange          (* tDateRangeType *),
      RelayManager           (* fmRelayManager *),
      RRFNotes               (* (just text) *),
      RRF_StringAndTimeUtils (* Plural *),
      SelectPrinter          (* fmSelectPrinter *),
      SensorManager          (* fmSensorManager *),
      ShellAPI               (* ShellExecute *),
      SimulatedSensor        (* fmSimulatedSensor *),
      SummaryManager         (* fmSummaryManager *),
      SysUtils               (* Abort, Beep, Format, FormatDateTime, Now,
                                  StrCopy *),
      TableManager           (* fmTableManager *),
      VC0706                 (* fmVC0706 *),
      WeatherThisDate        (* fmWeatherThisDate *);
      {$R *.dfm}

(********** infrastructure ****************************************************)
    procedure tfmHC4Main.CreateParams(var Params: tCreateParams);
      (* see http://www.delphidabbler.com/articles?article=13&part=2 *)

      begin (* tfmHC4Main.CreateParams *)
        inherited CreateParams(Params);
        StrCopy(Params.WinClassName, '2024-11-24 1102')
      end   (* tfmHC4Main.CreateParams *);

    procedure tfmHC4Main.FormClose(Sender: tObject; var Action: tCloseAction);
      (* break connection *)

      begin (* tfmHC4Main.FormClose *)
        case AskYesNoCancel('save logs?') of
          mrCancel: Abort;
             mrYes: begin (* save all logs *)
	              fmDashboard.SaveLog;
	              fmLogManager.SaveLogs;
	              fmGraphManager.SaveLog
	            end   (* save all logs *)
          end (* case *);

        timerLastInput.Enabled := false;
        fmeFrames.DisconnecteFrames;
        if Global.Status <> gsUnconnected then
          fmDashboard.ForceDisconnection
      end   (* tfmHC4Main.FormClose *);

    procedure tfmHC4Main.FormShow(Sender: tObject);
      (* initialize *)

      procedure ConnectMenuItems;
        (* natural package *)
                                                                 {$WARNINGS OFF}
        procedure MenuForm(TheItem: tMenuItem; TheForm: tForm);
          (* shortcut *)
        
          begin (* MenuForm *)
            TheItem.Tag := integer(TheForm)
          end   (* MenuForm *);
                                                                 {$WARNINGS ON}
        begin (* ConnectMenuItems *)
          RebuildGraphMenu;
          MenuForm(menuDashboard,             fmDashboard);
          MenuForm(menuBoardManager,          fmBoardManager);
          MenuForm(menuSensorManager,         fmSensorManager);
          MenuForm(menuSensorSimulated,       fmSimulatedSensor);
          MenuForm(menuSensorIndoor,          fmIndoorSensors);
          MenuForm(menuSensorsOutdoor,        fmOutdoorInstruments);
          MenuForm(menuTweakPhotocell,        fmFixPhotocell);
          MenuForm(menuUtilitiesPrinter,      fmSelectPrinter);
          MenuForm(menuUtilitiesTableManager, fmTableManager);
          MenuForm(menuUtilitiesWeather,      fmWeatherThisDate);
          MenuForm(menuVieweFrames,           fmeFrames);
          MenuForm(menuViewExtremes,          fmExtremes2);
          MenuForm(menuViewImageList,         fmVC0706);
          MenuForm(menuViewLog,               fmLogManager);
          MenuForm(menuViewNotes,             fmNoteManager);
          MenuForm(menuViewRelays,            fmRelayManager);
          MenuForm(menuViewSummary,           fmSummaryManager)
        end   (* ConnectMenuItems *);

      procedure ScaleWindow;
        (* pure UI adjustment *)

        begin (* ScaleWindow *)
          menuMain.OwnerDraw := true;
          with TheLabelAdjuster do
            case LeftMonitorScale of
              ms150: begin (* scale 150% *)
                       GutterLeft           := 171;
                       GutterRight          := 180;
                       HeaderLeft           :=   6;
                       HeaderTextSize       :=  16;
                       Screen.MenuFont.Size :=  10;
                       SimpleLeft           :=  78;
                       SmallTextSize        :=  12
                     end   (* scale 150% *);
              ms175: begin (* scale 175% *)
                       GutterLeft           := 171;
                       GutterRight          := 185;
                       HeaderLeft           :=   6;
                       HeaderTextSize       :=  14;
                       Screen.MenuFont.Size :=   8;
                       SimpleLeft           :=  78;
                       SmallTextSize        :=  11
                     end   (* scale 175% *);
              ms200: begin (* scale 200% *)
                       GutterLeft           := 171;
                       GutterRight          := 185;
                       HeaderLeft           :=   6;
                       HeaderTextSize       :=  12;
                       Screen.MenuFont.Size :=   8;
                       SimpleLeft           :=  78;
                       SmallTextSize        :=   9;
                     end   (* scale 200% *)
                else Screen.MenuFont.Size := 14
              end (* case *)
          end   (* ScaleWindow *);

      procedure SetUpTimer;
        (* LastInputTimer maintains status line *)

        const
          msTimerInterval = 6 * msOneSecond;

        begin (* SetUpTimer *)
          timerLastInput.Interval := msTimerInterval;
          timerLastInput.Enabled := true
        end   (* SetUpTimer *);

      begin (* tfmHC4Main.FormShow *)
        { globals }
            Global.Status := gsUnconnected;
            self.Caption  := FormatDateTime('"HC4 started "yyyy-mm-dd hhnn', Now);
            self.Color    := UsualFormColor;
            fmSelectPrinter.SetDefaultPrinter;
            fmTableManager.Initialize;

        { initialize outlying modules }
            fmAnnualTemps.Initialize;
            fmBoardManager.Initialize;
            fmDashboard.Initialize;
            fmeFrames.InitializeeFrames;
            fmGroupedOutsideGraph.Initialize;
            fmIndoorSensors.Initialize;
            fmOutdoorInstruments.Initialize;
            fmSimulatedSensor.Initialize;
            fmSummaryManager.UpdateIfNeeded;
            InitializeLabelAdjuster;
            fmGraphManager.Initialize;

       { local UI }
            lbBoardQuiet.Visible := false;
            lbMotionDetected.Visible := false;
            lbReboot.Visible := false;
            ScaleWindow;
            DrawMenuBar(Handle);
            ConnectMenuItems;
            SetUpTimer;
            DepthUpdateAppLoc := INILoadString('Pond', 'DepthUpdateApp', nullstring);

        { full UI }
            fmOutdoorInstruments.Show;
            fmIndoorSensors.Show;
            fmDashboard.Show
      end   (* tfmHC4Main.FormShow *);

    procedure tfmHC4Main.BoardQuiet(const BoardName: string;
                                    const Since: tDateTime);
      (* alert user to undue silence *)

      var
        Hours, Minutes: int64;

      begin (* tfmHC4Main.BoardQuiet *)
        Minutes := MinutesBetween(Now, Since);
        Hours := Minutes div 60;
        Minutes := Minutes mod 60;

        with lbBoardQuiet do
          begin (* lbBoardQuiet *)
            if Hours > 0 then
              begin (* long time *)
                Caption := Format('%s board quiet for %dh %dm', [BoardName, Hours, Minutes]);
                Font.Color := clRed
              end   (* long time *)
            else
              begin (* short time *)
                Caption := Format('%s board quiet for %s', [BoardName, Plural(Minutes, 'minute')]);
                Font.Color := clWindowText
              end   (* short time *);
            Visible := true
          end   (* lbBoardQuiet *)
      end   (* tfmHC4Main.BoardQuiet *);

    procedure tfmHC4Main.BoardRebooted(const TheMessage: string);
      (* called from BoardManager *)
    
      begin (* tfmHC4Main.BoardRebooted *)
        with lbReboot do
          begin (* lbReboot *)
            Caption := Format('%s at %s',
                              [TheMessage, FormatDateTime('hhnn', Now)]);
            Visible := true
          end   (* lbReboot *)
      end   (* tfmHC4Main.BoardRebooted *);

    procedure tfmHC4Main.MotionDetected(const Observed: string;
                                        const LocalTime: tDateTime);
      (* light up the message *)
    
      begin (* tfmHC4Main.MotionDetected *)
        with lbMotionDetected do
          begin (* lbMotionDetected *)
            Caption := Format('%s motion detected at %s',
                              [Observed, FormatDateTime('hhnn', LocalTime)]);
            Visible := true
          end   (* lbMotionDetected *)
      end   (* tfmHC4Main.MotionDetected *);

    procedure tfmHC4Main.timerLastInputTimer(Sender: tObject);
      (* report liveness of base-station connection *)

      const
        MinutesConnectionSuspicious = 1.5;
        MinutesConnectionBad        = 5;

      var
        msSinceInput: integer;
        MinutesSinceInput: real;

      begin (* tfmHC4Main.timerLastInputTimer *)
        lbTimeSinceInput.Font.Color := clWindowText;
        if Global.Status = gsUnconnected then
          lbTimeSinceInput.Caption := 'no communication'
        else
          begin (* interesting case *)
            msSinceInput := MilliSecondsBetween(Now, Global.LocalTimeLastCharReceived);
            MinutesSinceInput := (1.0 / msOneMinute) * msSinceInput;
            lbTimeSinceInput.Caption := Format('%.1f minutes since input',
                                               [MinutesSinceInput]);
            if MinutesSinceInput > MinutesConnectionBad then
              fmDashboard.LostConnection

            else if MinutesSinceInput > MinutesConnectionSuspicious then
              if not FreezeBeeped then
                begin (* new suspicion *)
                  lbTimeSinceInput.Font.Color := clRed;
                  Beep;
                  FreezeBeeped := true;
                  Show
                end   (* new suspicion *)
          end   (* interesting case *)
      end   (* tfmHC4Main.timerLastInputTimer *);

    procedure tfmHC4Main.UMEnsureRestored(var Msg: tMessage);
      (* wake up when user tries to start another instance *)

      begin (* tfmHC4Main.UMEnsureRestored *)
        if IsIconic(Application.Handle) then
            Application.Restore;
        if not Visible then
          Visible := true;
        Application.BringToFront;
        SetForegroundWindow(self.Handle)
      end   (* tfmHC4Main.UMEnsureRestored *);

(********** end infrastructure ************************************************)

(********** menu **************************************************************)
                                                                 {$WARNINGS OFF}
    procedure tfmHC4Main.ShowTheForm(Sender: tObject);
      (* show the form *)

      begin (* tfmHC4Main.ShowTheForm *)
        tForm(tMenuItem(Sender).Tag).Show;
      end   (* tfmHC4Main.ShowTheForm *);
                                                                 {$WARNINGS ON}
    procedure tfmHC4Main.menuArrangeClick(Sender: tObject);
      (* standard window arrangement now starts below clock gadget *)

      const
        HMargin     =   3;
        VMargin     =   5;
        TheWidth    = 500;
        ClockHeight = 140;

      var
        LeftPlace, NextTop: integer;

      procedure PlaceForm(TheForm: tForm);
        (* stack in upper right *)

        begin (* PlaceForm *)
          with TheForm do
            begin (* TheForm *)
              Top := NextTop;
              Left := LeftPlace;
              inc(NextTop, Height + VMargin);
              Width := TheWidth
            end   (* TheForm *);
        end   (* PlaceForm *);

      begin (* tfmHC4Main.menuArrangeClick *)
        LeftPlace := LeftMonitorRight[LeftMonitorScale] - TheWidth - HMargin;

        NextTop := ClockHeight + VMargin;
        PlaceForm(fmHC4Main);
        PlaceForm(fmOutdoorInstruments);
        PlaceForm(fmIndoorSensors)
      end   (* tfmHC4Main.menuArrangeClick *);

      //                                                                *** graphs
    procedure tfmHC4Main.GraphMenuClick(Sender: tObject);
      (* show the chosen graph *)

      begin (* tfmHC4Main.GraphMenuClick *)
        fmGraphManager.ShowGraph(tMenuItem(Sender).Tag, ' from main menu')
      end   (* tfmHC4Main.GraphMenuClick *);

    procedure tfmHC4Main.MenuEnabled(const WantEnabled: boolean);
      (* disable during dump *)

      var
        I: integer;

      begin (* tfmHC4Main.MenuEnabled *)
        for I := 1 to menuMain.Items.Count do
          menuMain.Items[I-1].Enabled := WantEnabled
      end   (* tfmHC4Main.MenuEnabled *);

    procedure tfmHC4Main.menuTweakWarnOffClick(Sender: tObject);
      (* stop the shouting *)

      begin (* tfmHC4Main.menuTweakWarnOffClick *)
        lbTimeSinceInput.Font.Color := clWindowText;
        FreezeBeeped                := false;
        lbMotionDetected.Visible    := false;
        lbReboot.Visible            := false
      end   (* tfmHC4Main.menuTweakWarnOffClick *);
//                                                                *** utilities
    procedure tfmHC4Main.menuUtilitiesDepthClick(Sender: tObject);
      (* separate application *)

      const
        Directory = nil;
        Params = nil;

      begin (* tfmHC4Main.menuUtilitiesDepthClick *)
                                                         {$Warn UNSAFE_TYPE OFF}
        ShellExecute(Handle,
                     'open',
                     pChar(DepthUpdateAppLoc),   // unsafe type
                     Params,
                     Directory,
                     SW_SHOWNORMAL)
                                                          {$Warn UNSAFE_TYPE ON}
      end   (* tfmHC4Main.menuUtilitiesDepthClick *);

    procedure tfmHC4Main.menuUtilitiesFurnaceFilterClick(Sender: tObject);
      (* find usage since date as seen in INI *)

      var
        FilterDate: tDate;
        HoursUsed: real;

      begin (* tfmHC4Main.menuUtilitiesFurnaceFilterClick *)
        FilterDate := INILoadDateTime('Main', 'FurnaceFilterChanged', Today);
        HoursUsed := 0;

        with qFilterHours do
          begin (* qFilterHours *)
            Close;
            ParamByName('FilterChangeDateTime').AsDate    := FilterDate;
            ParamByName('obFurnace'           ).AsInteger := ord(obFurnace);
            ParamByName('obAC'                ).AsInteger := ord(obAC);
            Open;
            while not EOF do
              begin (* one record *)
                HoursUsed := HoursUsed + Fields[0].AsFloat;
                Next
              end   (* one record *);
            Close
          end   (* qFilterHours *);

        ShowMessageFmt('filter used for %.1f hours since %s',
                       [HoursUsed, FormatDateTime('yyyy-mm-dd', FilterDate)])
      end   (* tfmHC4Main.menuUtilitiesFurnaceFilterClick *);

    procedure tfmHC4Main.menuUtilitiesResetFurnaceFilterClick(Sender: tObject);
      (* change in INI *)

      begin (* tfmHC4Main.menuUtilitiesResetFurnaceFilterClick *)
        if AskYesNo2('really reset furnace-filter date?') then
          INISaveDateTime('Main', 'FurnaceFilterChanged', Now)
      end   (* tfmHC4Main.menuUtilitiesResetFurnaceFilterClick *);

    procedure tfmHC4Main.RebuildGraphMenu;
     (* rebuild the Graphs menu to be

            generic graphs
            -----
            grouped outside graph
            wind graph
            -----
            graph manager
        *)

      const
        TopLevelName = 'graphs';

      var
        Frequent, Indoor, ShowGraphNumbers: boolean;
        GraphUID: integer;
        FrequentMenuItem, IndoorMenuItem, OutdoorMenuItem,
          TopLevelMenuItem: tMenuItem;
        TitleForMenu: string;

      function NewItem(ParentItem: tMenuItem;
                       const TheCaption: string;
                       FormToShow: tForm = nil): tMenuItem; forward;

      procedure AddGraph;
        (* Create a new menu item *)

        var
          DetailItem: tMenuItem;

        begin (* AddGraph *)
          // ShowMessageFmt('graph #%d in menu as %s', [GraphUID, TitleForMenu]);
          DetailItem := tMenuItem.Create(self);
          with DetailItem do
            begin (* DetailItem *)
              Caption := StringReplace(TitleForMenu, AMPERSAND, '&&', [rfReplaceAll]);
              if ShowGraphNumbers then
                Caption := Format('%s (%d)', [Caption, GraphUID]);
              Tag := GraphUID;
              OnClick := GraphMenuClick
            end   (* DetailItem *);

               if Frequent then FrequentMenuItem.Add(DetailItem)
          else if Indoor then   IndoorMenuItem.Add(DetailItem)
          else                  OutdoorMenuItem.Add(DetailItem)
        end   (* AddGraph *);

      procedure InitializeTopLevel;
        (* start with nothing below top-level selection *)

        var
          TopLevelNeeded: boolean;
          I: integer;

        begin (* InitializeTopLevel *)
          TopLevelNeeded := true;

          with menuMain do
            begin (* MainMenu *)
              for I := 1 to Items.Count do
                if StringReplace(Items[I-1].Caption,
                                  AMPERSAND,
                                  nullstring,
                                  []) = TopLevelName then
                  begin (* found it *)
                    TopLevelMenuItem := Items[I-1];
                    TopLevelMenuItem.Clear;
                    TopLevelNeeded := false;
                    break
                  end   (* found it *);

              if TopLevelNeeded then
                begin (* create new top-level item *)
                  TopLevelMenuItem := tMenuItem.Create(self);
                  TopLevelMenuItem.Caption := TopLevelName;
                  TopLevelMenuItem.Name := 'menu' + TopLevelName;
                  Items.Add(TopLevelMenuItem)
                end   (* create new top-level item *)
            end   (* MainMenu *)
        end   (* InitializeTopLevel *);
                                                                 {$WARNINGS OFF}
      function NewItem(ParentItem: tMenuItem;
                       const TheCaption: string;
                       FormToShow: tForm = nil): tMenuItem;
        (* just a shortcut to repeated code *)

        begin (* NewItem *)
          Result := tMenuItem.Create(self);
          Result.Caption := TheCaption;
          if assigned(FormToShow) then
            begin (* arrange for form to be shown *)
              Result.Tag := integer(FormToShow);
              Result.OnClick := ShowTheForm
            end   (* arrange for form to be shown *);

          ParentItem.Add(Result)
        end   (* NewItem *);
                                                                 {$WARNINGS ON}
      begin (* tfmHC4Main.RebuildGraphMenu *)
        InitializeTopLevel;
        ShowGraphNumbers := INILoadBoolean('Main', 'ShowGraphNumbers');
        FrequentMenuItem := NewItem(TopLevelMenuItem, 'frequent');
        IndoorMenuItem   := NewItem(TopLevelMenuItem, 'indoor');
        OutdoorMenuItem  := NewItem(TopLevelMenuItem, 'outdoor');

        with qGraphsForMenu do
          begin (* qGraphsForMenu *)
            Open;
            while not EOF do
              begin (* one record *)
                GraphUID      := FieldByName('UID'         ).AsInteger;
                Frequent      := FieldByName('Frequent'    ).AsBoolean;
                Indoor        := FieldByName('Indoor'      ).AsBoolean;
                TitleForMenu  := FieldByName('TitleForMenu').AsString;
                AddGraph;

//                if FieldByName('AutoStart').AsBoolean then
//                  if not RunningInIDE then
//                    fmGraphManager.ShowGraph(GraphUID);
                Next
              end   (* one record *);

            Close
          end   (* qGraphsForMenu *);

        NewItem(OutdoorMenuItem, HYPHEN);
        NewItem(OutdoorMenuItem, 'outside, grouped by date',  fmGroupedOutsideGraph);
        NewItem(OutdoorMenuItem, 'wind && particulates',      fmGraphWind);
        NewItem(OutdoorMenuItem, 'annual temperature ranges', fmAnnualTemps);

        NewItem(TopLevelMenuItem, HYPHEN);
        NewItem(TopLevelMenuItem, 'graph manager', fmGraphManager)
      end   (* tfmHC4Main.RebuildGraphMenu *);

    procedure tfmHC4Main.RunGraphManager(Sender: tObject);
      (* speciial-purpose notify event *)

      begin (* tfmHC4Main.RunGraphManager *)
        fmGraphManager.Show
      end   (* tfmHC4Main.RunGraphManager *);

    procedure tfmHC4Main.ShowGroupedOutsideGraph(Sender: tObject);
      (* speciial-purpose notify event *)

      begin (* tfmHC4Main.ShowGroupedOutsideGraph *)
        fmGroupedOutsideGraph.Show
      end   (* tfmHC4Main.ShowGroupedOutsideGraph *);

(********** end menu **********************************************************)

(********** unallocated *******************************************************)

end.
