unit NoteManager;

  interface
    uses
      AdsData   (* tAdsStringField *),
      AdsFunc   (* for AdsTable *),
      AdsTable  (* tAdsQuery *),
      AdvGrid   (* TMS *),
      AdvObj    (* TMS *),
      AdvUtil   (* TMS *),
      BaseGrid  (* TMS *),
      Classes   (* tStringList *),
      Controls  (**),
      DB        (* tDataSet, tDataSource, tDateTimeField, tMemoField,
                     tStringField *),
      DBAdvGrid (* tDBAdvGrid *),
      DBCtrls   (* tDBNavigator *),
      ExtCtrls  (* obscure need *),
      Forms     (* tForm *),
      Graphics  (* TMS *),
      Grids     (* TMS *),
      StdCtrls  (* tButton, tGroupBox, tMemo *),
      Windows   (* tRect *);

    type
      tfmNoteManager = class(tForm)
          qNoteCategories: tAdsQuery;
            dataNoteCategories: tDataSource;
              dbadvgridCategories: tDBAdvGrid;
          tbRRFNotes: tAdsTable;
            tbRRFNotesCategory: tAdsStringField;
            tbRRFNotesTimestamp: tDateTimeField;
            tbRRFNotesNote: tMemoField;
            tbRRFNotesXNote:      tStringField;
            tbRRFNotesXTimestamp: tStringField;
            dataRRFNotes: tDataSource;
              dbadvgridRRFNotes: tDBAdvGrid;
              navNotes: tDBNavigator;
          btnAddNewNote:     tButton;
          grpNote: tGroupBox;
            btnRecategorize:      tButton;
            btnRefreshCategories: tButton;
            btnUpdateNote:        tButton;
            lbedCategory: tLabeledEdit;
            memoNewNote: tMemo;
          memoCategoryNotes: tMemo;

          procedure qNoteCategoriesAfterScroll(DataSet: tDataSet);
          procedure tbRRFNotesAfterScroll     (DataSet: tDataSet);
          procedure tbRRFNotesCalcFields      (DataSet: tDataSet);

          procedure btnAddNewNoteClick       (Sender: tObject);
          procedure btnRecategorizeClick     (Sender: tObject);
          procedure btnRefreshCategoriesClick(Sender: tObject);
          procedure btnUpdateNoteClick       (Sender: tObject);
          procedure FormDblClick             (Sender: tObject);
          procedure FormShow                 (Sender: tObject);
          procedure memoNewNoteChange        (Sender: tObject);
        private
          procedure AggregateNotesOfCategory;
          function  PrettyPrintStamp: string;
        end (* tfmNoteManager *);

    var
      fmNoteManager: tfmNoteManager;

  implementation
    uses
      DateUtils      (* TimeOf *),
      DBAdvGridUtils (* SetUpDBAdvGridReadOnly *),
      HC4Main        (* fmHC4Main *),
      HC3Utilities   (* GetFieldWidth *),
      MyConsts       (* nullstring, UsualFormColor *),
      //MyUtils        (* EmphasizeSelectedDBGrinLine *),
      SysUtils       (* Format, FormatDateTime, Now, Trim *);
      {$R *.dfm}

    procedure tfmNoteManager.AggregateNotesOfCategory;
      (* build a big memo showing all of the notes of this category *)

      var
        I: integer;
        NoteMemo: tStringList;

      begin (* tfmNoteManager.AggregateNotesOfCategory *)
        memoCategoryNotes.Clear;
        NoteMemo := tStringList.Create;

        try     (* must free stringlist *)
          with tbRRFNotes do
            begin (* tbRRFNotes *)
              First;

              while not EOF do
                begin (* one record of notes *)
                  NoteMemo.Text := FieldByName('Note').AsString;

                  memoCategoryNotes.Lines.Add(Format('%13s%6s%s',
                                                     [PrettyPrintStamp,
                                                      nullstring,
                                                      Trim(NoteMemo[0])]));
                  for I := 2 to NoteMemo.Count do
                    memoCategoryNotes.Lines.Add(Format('%13s%4s%s',
                                                       [nullstring,
                                                        nullstring,
                                                        NoteMemo[I-1]]));
                  if NoteMemo.Count > 1 then
                    memoCategoryNotes.Lines.Add(nullstring);
                    
                  Next
                end   (* one record of notes *)
            end   (* tbRRFNotes *)
        finally (* must free stringlist *)
          NoteMemo.Free
        end     (* must free stringlist *)
      end   (* tfmNoteManager.AggregateNotesOfCategory *);

    procedure tfmNoteManager.btnAddNewNoteClick(Sender: tObject);
      (* add new note with today's date & current category *)

      var
        NewCategory, NewNote: string;

      begin (* tfmNoteManager.btnAddNewNoteClick *)
        NewCategory := Trim(lbedCategory.Text);
        NewNote := Trim(memoNewNote.Text);

        if NewNote <> nullstring then
          with tbRRFNotes do
            begin (* tbRRFNotes *)
              Insert;
              FieldByName('Category' ).AsString   := NewCategory;
              FieldByName('Note'     ).AsString   := NewNote;
              FieldByName('Timestamp').AsDateTime := Now;
              Post;
              Close;
              Open;
              AggregateNotesOfCategory
            end   (* tbRRFNotes *)
      end   (* tfmNoteManager.btnAddNewNoteClick *);

    procedure tfmNoteManager.btnRecategorizeClick(Sender: tObject);
      (* move note from one category to another *)

      var
        RevisedCategory, RevisedNote: string;

      begin (* tfmNoteManager.btnRecategorizeClick *)
        RevisedCategory := Trim(lbedCategory.Text);
        RevisedNote := Trim(memoNewNote.Text);

        if RevisedNote <> nullstring then
          if RevisedCategory <> nullstring then
            with tbRRFNotes do
              begin (* tbRRFNotes *)
                Edit;
                  FieldByName('Category').AsString := RevisedCategory;
                  FieldByName('Note'    ).AsString := RevisedNote;
                Post;
                AggregateNotesOfCategory
              end   (* tbRRFNotes *);

        btnUpdateNote.Enabled := false
      end   (* tfmNoteManager.btnRecategorizeClick *);

    procedure tfmNoteManager.btnRefreshCategoriesClick(Sender: tObject);
      (* updating may have added or deleted categories *)

      begin (* tfmNoteManager.btnRefreshCategoriesClick *)
        qNoteCategories.Close;
        qNoteCategories.Open
      end   (* tfmNoteManager.btnRefreshCategoriesClick *);

    procedure tfmNoteManager.btnUpdateNoteClick(Sender: tObject);
      (* replace old note *)

      var
        UpdateStamp: tDateTime;
        RevisedCategory, RevisedNote: string;

      begin (* tfmNoteManager.btnUpdateNoteClick *)
        RevisedCategory := Trim(lbedCategory.Text);
        RevisedNote := Trim(memoNewNote.Text);

        if RevisedNote <> nullstring then
          if RevisedCategory <> nullstring then
            with tbRRFNotes do
              begin (* tbRRFNotes *)
                UpdateStamp := FieldByName('Timestamp').AsDateTime;
                Edit;
                  FieldByName('Category').AsString := RevisedCategory;
                  FieldByName('Note'    ).AsString := RevisedNote;
                Post;
                AggregateNotesOfCategory;
                if not Locate('Timestamp', UpdateStamp, []) then
                  (* ignore it *)
              end   (* tbRRFNotes *);

        btnUpdateNote.Enabled := false
      end   (* tfmNoteManager.btnUpdateNoteClick *);

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

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

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

      begin (* tfmNoteManager.FormShow *)
        self.Color := UsualFormColor;
        lbedCategory.MaxLength := GetFieldWidth('RRFNotes', 'Category');
        tbRRFNotes.Open;
          SetUpDBAdvGridReadOnly(dbadvgridRRFNotes);

        qNoteCategories.Close;
        qNoteCategories.Open;
          SetUpDBAdvGridReadOnly(dbadvgridCategories)
      end   (* tfmNoteManager.FormShow *);

    procedure tfmNoteManager.memoNewNoteChange(Sender: tObject);
      (* old note changed; allow update *)

      begin (* tfmNoteManager.memoNewNoteChange *)
        btnUpdateNote.Enabled := true
      end   (* tfmNoteManager.memoNewNoteChange *);

    function tfmNoteManager.PrettyPrintStamp: string;
      (* avoid spurious 0000 times *)

      var
        RawStamp: tDateTime;

      begin (* tfmNoteManager.PrettyPrintStamp *)
        RawStamp := tbRRFNotes.FieldByName('Timestamp').AsDateTime;
        if TimeOf(RawStamp) = 0 then
          Result := FormatDateTime('  yyyy-MM-dd   ', RawStamp)
        else
          Result := FormatDateTime('yyyy-MM-dd hhnn', RawStamp)
      end   (* tfmNoteManager.PrettyPrintStamp *);

    procedure tfmNoteManager.qNoteCategoriesAfterScroll(DataSet: tDataSet);
      (* refilter table, and construct memo of all its notes *)

      begin (* tfmNoteManager.qNoteCategoriesAfterScroll *)
        with qNoteCategories do
          tbRRFNotes.Filter :=
            Format('Category =''%s''', [FieldByName('Category').AsString]);
        AggregateNotesOfCategory
      end   (* tfmNoteManager.qNoteCategoriesAfterScroll *);

    procedure tfmNoteManager.tbRRFNotesCalcFields(DataSet: tDataSet);
      (* prepare abridged note and prettyprinted timestamp *)

      var
        AbridgedMax: integer;
        XNote: string;

      begin (* tfmNoteManager.tbRRFNotesCalcFields *)
        AbridgedMax := tbRRFNotesXNote.Size;
        with tbRRFNotes do
          begin (* tbRRFNotes *)
            XNote := FieldByName('Note').AsString;
            if length(XNote) > AbridgedMax then
              XNote := copy(XNote, 1, AbridgedMax - 4) + ' ...';
            FieldByName('XNote').AsString := XNote;

            FieldByName('XTimestamp').AsString := PrettyPrintStamp
          end   (* tbRRFNotes *)
      end   (* tfmNoteManager.tbRRFNotesCalcFields *);

    procedure tfmNoteManager.tbRRFNotesAfterScroll(DataSet: tDataSet);
      (* move note to non-data-aware control *)

      begin (* tfmNoteManager.tbRRFNotesAfterScroll *)
        memoNewNote.Text  := tbRRFNotes.FieldByName('Note'    ).AsString;
        lbedCategory.Text := tbRRFNotes.FieldByName('Category').AsString;
        btnUpdateNote.Enabled := false
      end   (* tfmNoteManager.tbRRFNotesAfterScroll *);

end.
