[Back to OOP SWAG index]  [Back to Main SWAG index]  [Original]

unit clocks;
{$X+}  {allow discardable function results}

{ Clock-on-a-menubar OOP extension to Turbo Vision apps

  Copyright (c) 1990 by Danny Thorpe

  Alarms have not been implemented.
}

interface
uses dos, objects, drivers, views, menus, dialogs, app, msgbox;

const  cmClockChangeDisplay = 1001;
       cmClockSetAlarm = 1002;

       ClockNoSecs   = 0;
       ClockDispSecs = 1;
       Clock12hour   = 0;
       Clock24hour   = 1;

type

     ClockDataRec = record
       Format: word;
       Seconds: word;
       RefreshStr: String[2];
       end;


     PClockMenu = ^TClockMenu;
     TClockMenu = object(TMenuBar)
       ClockOptions: ClockDataRec;
       Refresh: byte;
       LastTime: DateTime;
       TimeStr: string[10];
       constructor Init(var Bounds: TRect; Amenu: PMenu);
       procedure Draw;   virtual;
       procedure Update; virtual;
       procedure SetRefresh(Secs: integer);        virtual;
       procedure SetRefreshStr( Secs: string);     virtual;
       procedure ClockChangeDisplay;               virtual;
       procedure HandleEvent( var Event: TEvent);  virtual;
       function  FormatTimeStr(h,m,s:word):string; virtual;
       end;




implementation


function LeadingZero(w : Word) : String;
var
  s : String;
begin
  Str(w:0,s);
  if Length(s) = 1 then
    s := '0' + s;
  LeadingZero := s;
end;



constructor TClockMenu.Init(var Bounds: TRect; AMenu: PMenu);
  var Temp: PMenuBar;
      ClockMenu: PMenu;
      R: TRect;
  begin
  ClockMenu:= NewMenu(NewSubMenu('~'#0'~Clock ', hcNoContext, NewMenu(
                NewItem('~C~hange display','',0,cmClockChangeDisplay, hcNoContext,
                NewItem('Set ~A~larm','', 0, cmClockSetAlarm, hcNoContext,
                nil))),
                AMenu^.Items));
                { ^^ tack passed menubar on end of new clock menu }
  ClockMenu^.Default:= AMenu^.Default;

  TMenuBar.Init(Bounds, ClockMenu);

  fillchar(LastTime,sizeof(LastTime),#$FF);   {fill with 65000's}
  TimeStr:='';
  ClockOptions.Format:= Clock24Hour;
  ClockOptions.Seconds:= ClockDispSecs;
  SetRefresh(1);
  end;



procedure TClockMenu.Draw;
  var P: PMenuItem;
  begin
  P:= FindItem(#0);
  if P <> nil then
    begin
    DisposeStr(P^.Name);
    P^.Name:= NewStr('~'#0'~'+TimeStr);
    end;
  TMenuBar.Draw;
  end;



procedure TClockMenu.Update;
  var h,m,s,hund: word;
  begin
    GetTime(h,m,s,hund);
    if abs(s-LastTime.sec) >= Refresh then
      begin
      with LastTime do
        begin
        Hour:=h;
        Min:=m;
        Sec:=s;
        end;
      TimeStr:= FormatTimeStr(h,m,s);
      DrawView;
      end;
  end;




procedure TClockMenu.SetRefresh(Secs: integer);
  begin
  if Secs > 59 then
    Secs := 59;
  if Secs < 0 then
    Secs := 0;
  Refresh:= Secs;
  Str(Refresh:2,ClockOptions.RefreshStr);
  end;



procedure TClockMenu.SetRefreshStr( Secs: string);
  var temp,code: integer;
  begin
  val(Secs, temp, code);
  if code = 0 then
    SetRefresh(temp);
  end;




procedure TClockMenu.ClockChangeDisplay;

  var
    D: PDialog;
    Control: PView;
    Command: word;
    temp,code: integer;
    R: TRect;
    ClockData : ClockDataRec;

  begin

  ClockData := ClockOptions;

  R.Assign(14,3,48,15);
  D:= new(PDialog, Init(R, 'Clock Display'));

  R.Assign(3,3,20,5);
  Control:= new(PRadioButtons, Init(R,
            NewSItem('~1~2 hour',
            NewSItem('~2~4 hour',
            nil))));
  D^.Insert(Control);

  R.Assign(3,2,20,3);
  Control:= new(Plabel, Init(R, '~F~ormat', Control));
  D^.Insert(Control);

  R.Assign(3,6,20,7);
  Control:= new(PCheckBoxes, Init(R,
            NewSItem('~S~econds',
            nil)));
  D^.Insert(Control);

  R.Assign(16,9,20,10);
  Control:= new(PInputLine, Init(R, 2));
  D^.Insert(Control);

  R.Assign(2,8,20,9);
  Control:= new(PLabel, Init(R, '~R~efresh Rate', Control));
  D^.Insert(Control);

  R.Assign(2,9,15,10);
  Control:= new(PLabel, Init(R, '0-59 seconds', PLabel(Control)^.Link));
  D^.Insert(Control);

  R.Assign(21,3,31,5);
  Control:= new(PButton, Init(R, '~O~k', cmOk, bfDefault));
  D^.Insert(Control);

  R.Assign(21,6,31,8);
  Control:= new(PButton, Init(R, '~C~ancel', cmCancel, bfNormal));
  D^.Insert(Control);


  D^.SelectNext(False);
  D^.SetData(ClockData);
  repeat
    Command:= Desktop^.ExecView(D);
    if Command = cmOK then
      begin
      D^.GetData(ClockData);
      val(ClockData.RefreshStr,temp,code);
      if (code <> 0) or ((temp<0) or (temp>59)) then
        MessageBox('Refresh rate must be between 0 and 59 seconds.',nil,
           mfOKButton+mfError);
      end;
  until (Command = cmCancel)
     or ((code=0) and ((temp>=0) and (temp<=59)));

  Dispose(D, Done);

  if Command = cmOk then
    begin
    ClockOptions:= ClockData;
    SetRefreshStr(ClockData.RefreshStr);
    end;

  { update display to reflect changes immediately }
  TimeStr:= FormatTimeStr(LastTime.hour, LastTime.min, LastTime.sec);
  DrawView;
  end;





procedure TClockMenu.HandleEvent( var Event: TEvent);
  begin
  TMenuBar.HandleEvent( Event);
  if Event.What = evCommand then
    begin
    case Event.Command of
      cmClockChangeDisplay: ClockChangeDisplay;
      cmClockSetAlarm: ;
      end;
    end;
  end;




function TClockMenu.FormatTimeStr(h,m,s: word): string;
  var st, tail: string;
  begin
  tail:='';
  if ClockOptions.Format = Clock24Hour then
    st:= LeadingZero(h)
  else
    begin
    if h >= 12 then
      begin
      tail:= 'pm';
      if h>12 then
        dec(h,12);
      end
    else
      tail:= 'am';
    if h=0 then h:=12;   {12 am}
    str(h:0,st);    { no leading space on hours }
    end;

  st:=st+':'+ LeadingZero(m);


  if ClockOptions.Seconds = ClockDispSecs then
    st:= st+':'+LeadingZero(s);

  FormatTimeStr:= st + tail;
  end;




end.

{ ----------------------------- DEMO  ---------------------- }

program TestPlatform;

uses Objects, Drivers, Views, Menus, App,
     Dos,     { for the paramcount and paramstr funcs}
     Clocks;  { for the clock on the menubar object, TClockMenu }

{ This generic test platform has been hooked up to the clock-on-the-menubar
  object / unit.  Search for *** to find hook-up points.

  Copyright (c) 1990 by Danny Thorpe
}


const  cmNewWin =   100;
       cmFileOpen = 101;

       WinCount : Integer = 0;
       MaxLines = 50;


type  PInterior = ^TInterior;
      TInterior = object(TScroller)
        constructor init(var Bounds: TRect; AHScrollbar, AVScrollbar: PScrollbar);
        procedure Draw;  virtual;
        end;


      PDemoWindow = ^TDemoWindow;
      TDemoWindow = object(TWindow)
        constructor Init(WindowNo: integer);
        end;


      TMyApp = object(TApplication)
        procedure InitStatusLine;  virtual;
        procedure InitMenuBar;  virtual;
        procedure NewWindow;
        procedure HandleEvent( var Event: TEvent); virtual;
        procedure Idle; virtual;
        end;


var MyApp: TMyApp;
    Lines: array [0..MaxLines-1] of PString;
    LineCount: Integer;


constructor TInterior.Init(var Bounds: TRect; AHScrollbar, AVScrollbar: PScrollbar);
  begin
  TScroller.Init(Bounds,AHScrollbar,AVScrollbar);
  Growmode := gfGrowHiX + gfGrowHiY;
  Options := Options or ofFramed;
  SetLimit(128,LineCount);
  end;


procedure TInterior.Draw;
  var color: byte;
      y,i: integer;
      B: TDrawBuffer;

  begin
  TScroller.Draw;
  Color := GetColor($01);
  for y:= 0 to Size.Y -1 do
    begin
    MoveChar(B,' ',Color,Size.X);
    I := Delta.Y + Y;
    if (I<Linecount) and (Lines[I] <> nil) then
      MoveStr(B,Copy(Lines[I]^,Delta.X+1,size.x),Color);
    WriteLine(0,y,size.x,1,B);
    end;
  end;


procedure ReadFile;
  var  F: text;
       S: string;

  begin
  LineCount:=0;
  if paramcount = 0 then
    assign(F,'clockwrk.pas')
  else
    assign(F,paramstr(1));
  reset(F);
  while not eof(F) and (linecount < maxlines) do
    begin
    readln(f,s);
    Lines[Linecount] := NewStr(S);
    Inc(LineCount);
    end;
  Close(F);
  end;





constructor TDemoWindow.Init(WindowNo: Integer);
  var  LInterior, RInterior: PInterior;
       HScrollbar, VScrollbar: PScrollbar;
       R: TRect;
       Center: integer;

  begin
    R.Assign(0,0,40,15);
    R.Move(Random(40),Random(8));

    TWindow.Init(R, 'Window', wnNoNumber);
    GetExtent(R);
    Center:= (R.B.X + R.A.X) div 2;
    R.Assign(Center,R.A.Y+1,Center+1,R.B.Y-1);
    VScrollbar:= new(PScrollbar, Init(R));
    with VScrollbar^ do Options := Options or ofPostProcess;
    Insert(VScrollbar);
    GetExtent(R);
    R.Assign(R.A.X+2,R.B.Y-1,Center-1,R.B.Y);
    HScrollbar:= new(PScrollbar, Init(R));
    with HScrollbar^ do Options := Options or ofPostProcess;
    Insert(HScrollbar);
    GetExtent(R);
    R.Assign(R.A.X+1,R.A.Y+1,Center,R.B.Y-1);
    LInterior:= new(PInterior, Init(R, HScrollbar, VScrollbar));
    with LInterior^ do
      begin
      Options:= Options or ofFramed;
      Growmode:= GrowMode or gfGrowHiX;
      SetLimit(128,LineCount);
      end;
    Insert(LInterior);

    GetExtent(R);
    R.Assign(R.B.X-1,R.A.Y+1,R.B.X,R.B.Y-1);
    VScrollbar:= new(PScrollbar, Init(R));
    with VScrollbar^ do Options := Options or ofPostProcess;
    Insert(VScrollbar);
    GetExtent(R);
    R.Assign(Center+2,R.B.Y-1,R.B.X-2,R.B.Y);
    HScrollbar:= new(PScrollbar, Init(R));
    with HScrollbar^ do
      begin
      Options := Options or ofPostProcess;
      GrowMode:= GrowMode or gfGrowLoX;
      end;
    Insert(HScrollbar);
    GetExtent(R);
    R.Assign(Center+1,R.A.Y+1,R.B.X-1,R.B.Y-1);
    RInterior:= new(PInterior, Init(R, HScrollbar, VScrollbar));
    with RInterior^ do
      begin
      Options:= Options or ofFramed;
      Growmode:= GrowMode or gfGrowLoX;
      SetLimit(128,LineCount);
      end;
    Insert(RInterior);
    end;




procedure TMyApp.InitStatusLine;
  var R: TRect;

  begin
  GetExtent(R);      { find out how big the current view is }
  R.A.Y := R.B.Y-1;  { squeeze R down to one line at bottom of frame }
  StatusLine := New(PStatusline, Init(R,
                  NewStatusDef(0, $FFFF,
                    NewStatusKey('~Alt-X~ Exit', kbAltX, cmQuit,
                    NewStatusKey('~F4~ New', kbF4, cmNewWin,
                    NewStatusKey('~Alt-F3~ Close', kbAltF3, cmClose,
                    nil))),
                  nil)
                ));
  end;


{ *** The vvv below indicate the primary hook-up point for the menubar-clock.
  This programmer-defined normal menu structure will be tacked onto the
  end of the clock menubar in TClockMenu.Init.
}

procedure TMyApp.InitMenuBar;
  var R: TRect;

  begin
  GetExtent(R);       {***}
  r.b.y:= r.a.y+1;   { vvv }
  Menubar := New(PClockMenu, Init(R, NewMenu(
               NewSubMenu('~F~ile', hcNoContext, NewMenu(
                 NewItem('~O~pen','F3', kbF3, cmFileOpen, hcNoContext,
                 NewItem('~N~ew','F4', kbF4, cmNewWin, hcNoContext,
                 NewLine(
                 NewItem('E~x~it','Alt-X', kbAltX, cmQuit, hcNoContext,
                 nil))))),
               NewSubMenu('~W~indow', hcNoContext, NewMenu(
                 NewItem('~N~ext','F6', kbF6, cmNext, hcNoContext,
                 NewItem('~Z~oom','F7', kbF7, cmZoom, hcNoContext,
                 nil))),
               nil))    { one ) for each menu defined }
             )));
  end;


procedure TMyApp.NewWindow;
  var
    Window: PDemoWindow;
    R: TRect;

  begin
  inc(WinCount);
  Window:= New(PDemoWindow, Init(WinCount));
  Desktop^.Insert(Window);
  end;




{*** clock hook-up point - typecasting required to access "new" method }

procedure TMyApp.Idle;
  begin
  TApplication.Idle;
  PClockMenu(MenuBar)^.Update;
  end;




procedure TMyApp.HandleEvent( var Event: TEvent);
  begin
  TApplication.HandleEvent(Event);
  if Event.What = evCommand then
    begin
      case Event.Command of
        cmNewWin: NewWindow;
      else  { case }
        Exit;
      end;  { case }
      ClearEvent(Event);
    end; {if}
  end;








begin

readfile;

MyApp.Init;
MyApp.run;
MyApp.done;
end.

[Back to OOP SWAG index]  [Back to Main SWAG index]  [Original]