home *** CD-ROM | disk | FTP | other *** search
/ BBS 1 / BBS#1.iso / for-dos / newtvsrc.arj / MENUS.PAS < prev    next >
Pascal/Delphi Source File  |  1994-04-04  |  32KB  |  1,417 lines

  1.  
  2. {*******************************************************}
  3. {                                                       }
  4. {       Turbo Pascal Version 7.0                        }
  5. {       Turbo Vision Unit                               }
  6. {                                                       }
  7. {       Copyright (c) 1992 Borland International        }
  8. {       Copyright (c) 1993 ACD Group                    }
  9. {*******************************************************}
  10.  
  11. unit Menus;
  12.  
  13. {$O+,F+,X+,I-,S-}
  14.  
  15. interface
  16.  
  17. uses Objects, Drivers, Views;
  18.  
  19. const
  20.  
  21. { Color palettes }
  22.  
  23.   CMenuView   = #2#3#4#5#6#7;
  24.   CMenuPopup  = #11#12#13#14#15#16;
  25.   CStatusLine = #2#3#4#5#6#7;
  26.  
  27. type
  28.  
  29. { TMenu types }
  30.  
  31.   TMenuStr = string[31];
  32.  
  33.   PMenu = ^TMenu;
  34.  
  35.   PMenuItem = ^TMenuItem;
  36.   TMenuItem = record
  37.     Next: PMenuItem;
  38.     Name: PString;
  39.     Command: Word;
  40.     Disabled: Boolean;
  41.     KeyCode: Word;
  42.     HelpCtx: Word;
  43.     case Integer of
  44.       0: (Param: PString);
  45.       1: (SubMenu: PMenu);
  46.   end;
  47.  
  48.   TMenu = record
  49.     Items: PMenuItem;
  50.     Default: PMenuItem;
  51.   end;
  52.  
  53. { TMenuView object }
  54.  
  55.   { Palette layout }
  56.   { 1 = Normal text }
  57.   { 2 = Disabled text }
  58.   { 3 = Shortcut text }
  59.   { 4 = Normal selection }
  60.   { 5 = Disabled selection }
  61.   { 6 = Shortcut selection }
  62.  
  63.   PMenuView = ^TMenuView;
  64.   TMenuView = object(TView)
  65.     ParentMenu: PMenuView;
  66.     Menu: PMenu;
  67.     Current: PMenuItem;
  68.     constructor Init(var Bounds: TRect);
  69.     constructor Load(var S: TStream);
  70.     function Execute: Word; virtual;
  71.     function FindItem(Ch: Char): PMenuItem;
  72.     procedure GetItemRect(Item: PMenuItem; var R: TRect); virtual;
  73.     function GetHelpCtx: Word; virtual;
  74.     function GetPalette: PPalette; virtual;
  75.     procedure HandleEvent(var Event: TEvent); virtual;
  76.     function HotKey(KeyCode: Word): PMenuItem;
  77.     function NewSubView(var Bounds: TRect; AMenu: PMenu;
  78.       AParentMenu: PMenuView): PMenuView; virtual;
  79.     procedure Store(var S: TStream);
  80.   end;
  81.  
  82. { TMenuBar object }
  83.  
  84.   { Palette layout }
  85.   { 1 = Normal text }
  86.   { 2 = Disabled text }
  87.   { 3 = Shortcut text }
  88.   { 4 = Normal selection }
  89.   { 5 = Disabled selection }
  90.   { 6 = Shortcut selection }
  91.  
  92.   PMenuBar = ^TMenuBar;
  93.   TMenuBar = object(TMenuView)
  94.     constructor Init(var Bounds: TRect; AMenu: PMenu);
  95.     destructor Done; virtual;
  96.     procedure Draw; virtual;
  97.     procedure GetItemRect(Item: PMenuItem; var R: TRect); virtual;
  98.   end;
  99.  
  100. { TMenuBox object }
  101.  
  102.   { Palette layout }
  103.   { 1 = Normal text }
  104.   { 2 = Disabled text }
  105.   { 3 = Shortcut text }
  106.   { 4 = Normal selection }
  107.   { 5 = Disabled selection }
  108.   { 6 = Shortcut selection }
  109.  
  110.   PMenuBox = ^TMenuBox;
  111.   TMenuBox = object(TMenuView)
  112.     constructor Init(var Bounds: TRect; AMenu: PMenu;
  113.       AParentMenu: PMenuView);
  114.     procedure Draw; virtual;
  115.     procedure GetItemRect(Item: PMenuItem; var R: TRect); virtual;
  116.   end;
  117.  
  118. { TMenuPopup object }
  119.  
  120.   { Palette layout }
  121.   { 1 = Normal text }
  122.   { 2 = Disabled text }
  123.   { 3 = Shortcut text }
  124.   { 4 = Normal selection }
  125.   { 5 = Disabled selection }
  126.   { 6 = Shortcut selection }
  127.  
  128.   PMenuPopup = ^TMenuPopup;
  129.   TMenuPopup = object(TMenuBox)
  130.     constructor Init(var Bounds: TRect; AMenu: PMenu);
  131.     destructor Done; virtual;
  132.     procedure HandleEvent(var Event: TEvent); virtual;
  133.     function GetPalette: PPalette; virtual;
  134.   end;
  135.  
  136. { TStatusItem }
  137.  
  138.   PStatusItem = ^TStatusItem;
  139.   TStatusItem = record
  140.     Next: PStatusItem;
  141.     Text: PString;
  142.     KeyCode: Word;
  143.     Command: Word;
  144.   end;
  145.  
  146. { TStatusDef }
  147.  
  148.   PStatusDef = ^TStatusDef;
  149.   TStatusDef = record
  150.     Next: PStatusDef;
  151.     Min, Max: Word;
  152.     Items: PStatusItem;
  153.   end;
  154.  
  155. { TStatusLine }
  156.  
  157.   { Palette layout }
  158.   { 1 = Normal text }
  159.   { 2 = Disabled text }
  160.   { 3 = Shortcut text }
  161.   { 4 = Normal selection }
  162.   { 5 = Disabled selection }
  163.   { 6 = Shortcut selection }
  164.  
  165.   PStatusLine = ^TStatusLine;
  166.   TStatusLine = object(TView)
  167.     Items: PStatusItem;
  168.     Defs: PStatusDef;
  169.     constructor Init(var Bounds: TRect; ADefs: PStatusDef);
  170.     constructor Load(var S: TStream);
  171.     destructor Done; virtual;
  172.     procedure Draw; virtual;
  173.     function GetPalette: PPalette; virtual;
  174.     procedure HandleEvent(var Event: TEvent); virtual;
  175.     function Hint(AHelpCtx: Word): String; virtual;
  176.     procedure Store(var S: TStream);
  177.     procedure Update; virtual;
  178.   private
  179.     procedure DrawSelect(Selected: PStatusItem);
  180.     procedure FindItems;
  181.   end;
  182.  
  183. { TMenuItem routines }
  184.  
  185. function NewItem(Name, Param: TMenuStr; KeyCode: Word; Command: Word;
  186.   AHelpCtx: Word; Next: PMenuItem): PMenuItem;
  187. function NewLine(Next: PMenuItem): PMenuItem;
  188. function NewSubMenu(Name: TMenuStr; AHelpCtx: Word; SubMenu: PMenu;
  189.   Next: PMenuItem): PMenuItem;
  190.  
  191. { TMenu routines }
  192.  
  193. function NewMenu(Items: PMenuItem): PMenu;
  194. procedure DisposeMenu(Menu: PMenu);
  195.  
  196. { TStatusLine routines }
  197.  
  198. function NewStatusDef(AMin, AMax: Word; AItems: PStatusItem;
  199.   ANext: PStatusDef): PStatusDef;
  200. function NewStatusKey(const AText: String; AKeyCode: Word; ACommand: Word;
  201.   ANext: PStatusItem): PStatusItem;
  202.  
  203. { StatusLine messaging }
  204.  
  205. procedure StatusMessage(Message: string);
  206. procedure ClearStatusMessage;
  207.  
  208. { Menus registration procedure }
  209.  
  210. procedure RegisterMenus;
  211.  
  212. { Stream registration records }
  213.  
  214. const
  215.   RMenuBar: TStreamRec = (
  216.      ObjType: 40;
  217.      VmtLink: Ofs(TypeOf(TMenuBar)^);
  218.      Load:    @TMenuBar.Load;
  219.      Store:   @TMenuBar.Store
  220.   );
  221.  
  222. const
  223.   RMenuBox: TStreamRec = (
  224.      ObjType: 41;
  225.      VmtLink: Ofs(TypeOf(TMenuBox)^);
  226.      Load:    @TMenuBox.Load;
  227.      Store:   @TMenuBox.Store
  228.   );
  229.  
  230. const
  231.   RStatusLine: TStreamRec = (
  232.      ObjType: 42;
  233.      VmtLink: Ofs(TypeOf(TStatusLine)^);
  234.      Load:    @TStatusLine.Load;
  235.      Store:   @TStatusLine.Store
  236.   );
  237.  
  238. const
  239.   RMenuPopup: TStreamRec = (
  240.      ObjType: 43;
  241.      VmtLink: Ofs(TypeOf(TMenuPopup)^);
  242.      Load:    @TMenuPopup.Load;
  243.      Store:   @TMenuPopup.Store
  244.   );
  245.  
  246.  
  247. implementation
  248.  
  249. uses
  250.   App;
  251.  
  252. { TMenuItem routines }
  253.  
  254. function NewItem(Name, Param: TMenuStr; KeyCode: Word; Command: Word;
  255.   AHelpCtx: Word; Next: PMenuItem): PMenuItem;
  256. const
  257.   T: PView = nil;
  258. var
  259.   P: PMenuItem;
  260. begin
  261.   if (Name <> '') and (Command <> 0) then
  262.   begin
  263.     New(P);
  264.     P^.Next := Next;
  265.     P^.Name := NewStr(Name);
  266.     P^.Command := Command;
  267.     P^.Disabled := not T^.CommandEnabled(Command);
  268.     P^.KeyCode := KeyCode;
  269.     P^.HelpCtx := AHelpCtx;
  270.     P^.Param := NewStr(Param);
  271.     NewItem := P;
  272.   end else
  273.   NewItem := Next;
  274. end;
  275.  
  276. function NewLine(Next: PMenuItem): PMenuItem;
  277. var
  278.   P: PMenuItem;
  279. begin
  280.   New(P);
  281.   P^.Next := Next;
  282.   P^.Name := nil;
  283.   P^.HelpCtx := hcNoContext;
  284.   NewLine := P;
  285. end;
  286.  
  287. function NewSubMenu(Name: TMenuStr; AHelpCtx: Word; SubMenu: PMenu;
  288.   Next: PMenuItem): PMenuItem;
  289. var
  290.   P: PMenuItem;
  291. begin
  292.   if (Name <> '') and (SubMenu <> nil) then
  293.   begin
  294.     New(P);
  295.     P^.Next := Next;
  296.     P^.Name := NewStr(Name);
  297.     P^.Command := 0;
  298.     P^.Disabled := False;
  299.     P^.HelpCtx := AHelpCtx;
  300.     P^.SubMenu := SubMenu;
  301.     NewSubMenu := P;
  302.   end else
  303.   NewSubMenu := Next;
  304. end;
  305.  
  306. { TMenu routines }
  307.  
  308. function NewMenu(Items: PMenuItem): PMenu;
  309. var
  310.   P: PMenu;
  311. begin
  312.   New(P);
  313.   P^.Items := Items;
  314.   P^.Default := Items;
  315.   NewMenu := P;
  316. end;
  317.  
  318. procedure DisposeMenu(Menu: PMenu);
  319. var
  320.   P, Q: PMenuItem;
  321. begin
  322.   if Menu <> nil then
  323.   begin
  324.     P := Menu^.Items;
  325.     while P <> nil do
  326.     begin
  327.       if P^.Name <> nil then
  328.       begin
  329.         DisposeStr(P^.Name);
  330.         if P^.Command <> 0 then
  331.           DisposeStr(P^.Param) else
  332.           DisposeMenu(P^.SubMenu);
  333.       end;
  334.       Q := P;
  335.       P := P^.Next;
  336.       Dispose(Q);
  337.     end;
  338.     Dispose(Menu);
  339.   end;
  340. end;
  341.  
  342. { TMenuView }
  343.  
  344. constructor TMenuView.Init(var Bounds: TRect);
  345. begin
  346.   TView.Init(Bounds);
  347.   EventMask := EventMask or evBroadcast;
  348. end;
  349.  
  350. constructor TMenuView.Load(var S: TStream);
  351.  
  352. function DoLoadMenu: PMenu;
  353. var
  354.   Item: PMenuItem;
  355.   Last: ^PMenuItem;
  356.   Menu: PMenu;
  357.   Tok: Byte;
  358. begin
  359.   New(Menu);
  360.   Last := @Menu^.Items;
  361.   Item := nil;
  362.   S.Read(Tok,1);
  363.   while Tok <> 0 do
  364.   begin
  365.     New(Item);
  366.     Last^ := Item;
  367.     Last := @Item^.Next;
  368.     with Item^ do
  369.     begin
  370.       Name := S.ReadStr;
  371.       S.Read(Command, SizeOf(Word) * 3 + SizeOf(Boolean));
  372.       if (Name <> nil) then
  373.         if Command = 0 then SubMenu := DoLoadMenu
  374.         else Param := S.ReadStr;
  375.     end;
  376.     S.Read(Tok, 1);
  377.   end;
  378.   Last^ := nil;
  379.   Menu^.Default := Menu^.Items;
  380.   DoLoadMenu := Menu;
  381. end;
  382.  
  383. begin
  384.   TView.Load(S);
  385.   Menu := DoLoadMenu;
  386. end;
  387.  
  388. function TMenuView.Execute: Word;
  389. type
  390.   MenuAction = (DoNothing, DoSelect, DoReturn);
  391. var
  392.   AutoSelect: Boolean;
  393.   Action: MenuAction;
  394.   Ch: Char;
  395.   Result: Word;
  396.   ItemShown, P: PMenuItem;
  397.   Target: PMenuView;
  398.   R: TRect;
  399.   E: TEvent;
  400.   MouseActive: Boolean;
  401.  
  402. procedure TrackMouse;
  403. var
  404.   Mouse: TPoint;
  405.   R: TRect;
  406. begin
  407.   MakeLocal(E.Where, Mouse);
  408.   Current := Menu^.Items;
  409.   while Current <> nil do
  410.   begin
  411.     GetItemRect(Current, R);
  412.     if R.Contains(Mouse) then
  413.     begin
  414.       MouseActive := True;
  415.       Exit;
  416.     end;
  417.     Current := Current^.Next;
  418.   end;
  419. end;
  420.  
  421. procedure TrackKey(FindNext: Boolean);
  422.  
  423. procedure NextItem;
  424. begin
  425.   Current := Current^.Next;
  426.   if Current = nil then Current := Menu^.Items;
  427. end;
  428.  
  429. procedure PrevItem;
  430. var
  431.   P: PMenuItem;
  432. begin
  433.   P := Current;
  434.   if P = Menu^.Items then P := nil;
  435.   repeat NextItem until Current^.Next = P;
  436. end;
  437.  
  438. begin
  439.   if Current <> nil then
  440.     repeat
  441.       if FindNext then NextItem else PrevItem;
  442.     until Current^.Name <> nil;
  443. end;
  444.  
  445. function MouseInOwner: Boolean;
  446. var
  447.   Mouse: TPoint;
  448.   R: TRect;
  449. begin
  450.   MouseInOwner := False;
  451.   if (ParentMenu <> nil) and (ParentMenu^.Size.Y = 1) then
  452.   begin
  453.     ParentMenu^.MakeLocal(E.Where, Mouse);
  454.     ParentMenu^.GetItemRect(ParentMenu^.Current, R);
  455.     MouseInOwner := R.Contains(Mouse);
  456.   end;
  457. end;
  458.  
  459. function MouseInMenus: Boolean;
  460. var
  461.   P: PMenuView;
  462. begin
  463.   P := ParentMenu;
  464.   while (P <> nil) and not P^.MouseInView(E.Where) do P := P^.ParentMenu;
  465.   MouseInMenus := P <> nil;
  466. end;
  467.  
  468. function TopMenu: PMenuView;
  469. var
  470.   P: PMenuView;
  471. begin
  472.   P := @Self;
  473.   while P^.ParentMenu <> nil do P := P^.ParentMenu;
  474.   TopMenu := P;
  475. end;
  476.  
  477. begin
  478.   AutoSelect := False;
  479.   Result := 0;
  480.   ItemShown := nil;
  481.   Current := Menu^.Default;
  482.   MouseActive := False;
  483.   repeat
  484.     Action := DoNothing;
  485.     GetEvent(E);
  486.     case E.What of
  487.       evMouseDown:
  488.         if MouseInView(E.Where) or MouseInOwner then
  489.         begin
  490.           TrackMouse;
  491.           if Size.Y = 1 then AutoSelect := True;
  492.         end else Action := DoReturn;
  493.       evMouseUp:
  494.         begin
  495.           TrackMouse;
  496.           if MouseInOwner then
  497.             Current := Menu^.Default
  498.           else
  499.             if (Current <> nil) and (Current^.Name <> nil) then
  500.               Action := DoSelect
  501.             else
  502.               if MouseActive or MouseInView(E.Where) then Action := DoReturn
  503.               else
  504.               begin
  505.                 Current := Menu^.Default;
  506.                 if Current = nil then Current := Menu^.Items;
  507.                 Action := DoNothing;
  508.               end;
  509.         end;
  510.       evMouseMove:
  511.         if E.Buttons <> 0 then
  512.         begin
  513.           TrackMouse;
  514.           if not (MouseInView(E.Where) or MouseInOwner) and
  515.             MouseInMenus then Action := DoReturn;
  516.         end;
  517.       evKeyDown:
  518.         case CtrlToArrow(E.KeyCode) of
  519.           kbUp, kbDown:
  520.             if Size.Y <> 1 then
  521.               TrackKey(CtrlToArrow(E.KeyCode) = kbDown) else
  522.               if E.KeyCode = kbDown then AutoSelect := True;
  523.           kbLeft, kbRight:
  524.             if ParentMenu = nil then
  525.               TrackKey(CtrlToArrow(E.KeyCode) = kbRight) else
  526.               Action := DoReturn;
  527.           kbHome, kbEnd:
  528.             if Size.Y <> 1 then
  529.             begin
  530.               Current := Menu^.Items;
  531.               if E.KeyCode = kbEnd then TrackKey(False);
  532.             end;
  533.           kbEnter:
  534.             begin
  535.               if Size.Y = 1 then AutoSelect := True;
  536.               Action := DoSelect;
  537.             end;
  538.           kbEsc:
  539.             begin
  540.               Action := DoReturn;
  541.               if (ParentMenu = nil) or (ParentMenu^.Size.Y <> 1) then
  542.                 ClearEvent(E);
  543.             end;
  544.         else
  545.           Target := @Self;
  546.           Ch := GetAltChar(E.KeyCode);
  547.           if Ch = #0 then Ch := E.CharCode else Target := TopMenu;
  548.           P := Target^.FindItem(Ch);
  549.           if P = nil then
  550.           begin
  551.             P := TopMenu^.HotKey(E.KeyCode);
  552.             if (P <> nil) and CommandEnabled(P^.Command) then
  553.             begin
  554.               Result := P^.Command;
  555.               Action := DoReturn;
  556.             end
  557.           end else
  558.             if Target = @Self then
  559.             begin
  560.               if Size.Y = 1 then AutoSelect := True;
  561.               Action := DoSelect;
  562.               Current := P;
  563.             end else
  564.               if (ParentMenu <> Target) or (ParentMenu^.Current <> P) then
  565.                 Action := DoReturn;
  566.         end;
  567.       evCommand:
  568.         if E.Command = cmMenu then
  569.         begin
  570.           AutoSelect := False;
  571.           if ParentMenu <> nil then Action := DoReturn;
  572.         end else Action := DoReturn;
  573.     end;
  574.     if ItemShown <> Current then
  575.     begin
  576.       ItemShown := Current;
  577.       DrawView;
  578.     end;
  579.     if (Action = DoSelect) or ((Action = DoNothing) and AutoSelect) then
  580.       if Current <> nil then with Current^ do if Name <> nil then
  581.         if Command = 0 then
  582.         begin
  583.           if E.What and (evMouseDown + evMouseMove) <> 0 then PutEvent(E);
  584.           GetItemRect(Current, R);
  585.           R.A.X := R.A.X + Origin.X;
  586.           R.A.Y := R.B.Y + Origin.Y;
  587.           R.B := Owner^.Size;
  588.           Target := TopMenu^.NewSubView(R, SubMenu, @Self);
  589.           Result := Owner^.ExecView(Target);
  590.           Dispose(Target, Done);
  591.         end else if Action = DoSelect then Result := Command;
  592.     if (Result <> 0) and CommandEnabled(Result) then
  593.     begin
  594.       Action := DoReturn;
  595.       ClearEvent(E);
  596.     end
  597.     else
  598.       Result := 0;
  599.   until Action = DoReturn;
  600.   if E.What <> evNothing then
  601.     if (ParentMenu <> nil) or (E.What = evCommand) then PutEvent(E);
  602.   if Current <> nil then
  603.   begin
  604.     Menu^.Default := Current;
  605.     Current := nil;
  606.     DrawView;
  607.   end;
  608.   Execute := Result;
  609. end;
  610.  
  611. function TMenuView.FindItem(Ch: Char): PMenuItem;
  612. var
  613.   P: PMenuItem;
  614.   I: Integer;
  615. begin
  616.   Ch := UpCase(Ch);
  617.   P := Menu^.Items;
  618.   while P <> nil do
  619.   begin
  620.     if (P^.Name <> nil) and not P^.Disabled then
  621.     begin
  622.       I := Pos('~', P^.Name^);
  623.       if (I <> 0) and (Ch = UpCase(P^.Name^[I + 1])) then
  624.       begin
  625.         FindItem := P;
  626.         Exit;
  627.       end;
  628.     end;
  629.     P := P^.Next;
  630.   end;
  631.   FindItem := nil;
  632. end;
  633.  
  634. procedure TMenuView.GetItemRect(Item: PMenuItem; var R: TRect);
  635. begin
  636. end;
  637.  
  638. function TMenuView.GetHelpCtx: Word;
  639. var
  640.   C: PMenuView;
  641. begin
  642.   C := @Self;
  643.   while (C <> nil) and
  644.      ((C^.Current = nil) or (C^.Current^.HelpCtx = hcNoContext) or
  645.       (C^.Current^.Name = nil)) do
  646.     C := C^.ParentMenu;
  647.   if C <> nil then GetHelpCtx := C^.Current^.HelpCtx
  648.   else GetHelpCtx := hcNoContext;
  649. end;
  650.  
  651. function TMenuView.GetPalette: PPalette;
  652. const
  653.   P: string[Length(CMenuView)] = CMenuView;
  654. begin
  655.   GetPalette := @P;
  656. end;
  657.  
  658. procedure TMenuView.HandleEvent(var Event: TEvent);
  659. var
  660.   CallDraw: Boolean;
  661.   P: PMenuItem;
  662.  
  663. procedure UpdateMenu(Menu: PMenu);
  664. var
  665.   P: PMenuItem;
  666.   CommandState: Boolean;
  667. begin
  668.   P := Menu^.Items;
  669.   while P <> nil do
  670.   begin
  671.     if P^.Name <> nil then
  672.       if P^.Command = 0 then UpdateMenu(P^.SubMenu)
  673.       else
  674.       begin
  675.         CommandState := CommandEnabled(P^.Command);
  676.         if P^.Disabled = CommandState then
  677.         begin
  678.           P^.Disabled := not CommandState;
  679.           CallDraw := True;
  680.         end;
  681.       end;
  682.     P := P^.Next;
  683.   end;
  684. end;
  685.  
  686. procedure DoSelect;
  687. begin
  688.   PutEvent(Event);
  689.   Event.Command := Owner^.ExecView(@Self);
  690.   if (Event.Command <> 0) and CommandEnabled(Event.Command) then
  691.   begin
  692.     Event.What := evCommand;
  693.     Event.InfoPtr := nil;
  694.     PutEvent(Event);
  695.   end;
  696.   ClearEvent(Event);
  697. end;
  698.  
  699. begin
  700.   if Menu <> nil then
  701.     case Event.What of
  702.       evMouseDown:
  703.         DoSelect;
  704.       evKeyDown:
  705.         if (FindItem(GetAltChar(Event.KeyCode)) <> nil) then
  706.           DoSelect
  707.         else
  708.         begin
  709.           P := HotKey(Event.KeyCode);
  710.           if (P <> nil) and (CommandEnabled(P^.Command)) then
  711.           begin
  712.             Event.What := evCommand;
  713.             Event.Command := P^.Command;
  714.             Event.InfoPtr := nil;
  715.             PutEvent(Event);
  716.             ClearEvent(Event);
  717.           end;
  718.         end;
  719.       evCommand:
  720.         if Event.Command = cmMenu then DoSelect;
  721.       evBroadcast:
  722.         if Event.Command = cmCommandSetChanged then
  723.         begin
  724.           CallDraw := False;
  725.           UpdateMenu(Menu);
  726.           if CallDraw then DrawView;
  727.         end;
  728.     end;
  729. end;
  730.  
  731. function TMenuView.HotKey(KeyCode: Word): PMenuItem;
  732.  
  733. function FindHotKey(P: PMenuItem): PMenuItem;
  734. var
  735.   T: PMenuItem;
  736. begin
  737.   while P <> nil do
  738.   begin
  739.     if P^.Name <> nil then
  740.       if P^.Command = 0 then
  741.       begin
  742.         T := FindHotKey(P^.SubMenu^.Items);
  743.         if T <> nil then
  744.         begin
  745.           FindHotKey := T;
  746.           Exit;
  747.         end;
  748.       end
  749.       else if not P^.Disabled and (P^.KeyCode <> kbNoKey) and
  750.         (P^.KeyCode = KeyCode) then
  751.       begin
  752.         FindHotKey := P;
  753.         Exit;
  754.       end;
  755.     P := P^.Next;
  756.   end;
  757.   FindHotKey := nil;
  758. end;
  759.  
  760. begin
  761.   HotKey := FindHotKey(Menu^.Items);
  762. end;
  763.  
  764. function TMenuView.NewSubView(var Bounds: TRect; AMenu: PMenu;
  765.   AParentMenu: PMenuView): PMenuView;
  766. begin
  767.   NewSubView := New(PMenuBox, Init(Bounds, AMenu, AParentMenu));
  768. end;
  769.  
  770. procedure TMenuView.Store(var S: TStream);
  771.  
  772. procedure DoStoreMenu(Menu: PMenu);
  773. var
  774.   Item: PMenuItem;
  775.   Tok: Byte;
  776. begin
  777.   Tok := $FF;
  778.   Item := Menu^.Items;
  779.   while Item <> nil do
  780.   begin
  781.     with Item^ do
  782.     begin
  783.       S.Write(Tok, 1);
  784.       S.WriteStr(Name);
  785.       S.Write(Command, SizeOf(Word) * 3 + SizeOf(Boolean));
  786.       if (Name <> nil) then
  787.         if Command = 0 then DoStoreMenu(SubMenu)
  788.         else S.WriteStr(Param);
  789.     end;
  790.     Item := Item^.Next;
  791.   end;
  792.   Tok := 0;
  793.   S.Write(Tok, 1);
  794. end;
  795.  
  796. begin
  797.   TView.Store(S);
  798.   DoStoreMenu(Menu);
  799. end;
  800.  
  801. { TMenuBar }
  802.  
  803. constructor TMenuBar.Init(var Bounds: TRect; AMenu: PMenu);
  804. begin
  805.   TMenuView.Init(Bounds);
  806.   GrowMode := gfGrowHiX;
  807.   Menu := AMenu;
  808.   Options := Options or ofPreProcess;
  809. end;
  810.  
  811. destructor TMenuBar.Done;
  812. begin
  813.   TMenuView.Done;
  814.   DisposeMenu(Menu);
  815. end;
  816.  
  817. procedure TMenuBar.Draw;
  818. var
  819.   X, L: Integer;
  820.   CNormal, CSelect, CNormDisabled, CSelDisabled, Color: Word;
  821.   P: PMenuItem;
  822.   B: TDrawBuffer;
  823. begin
  824.   CNormal := GetColor($0301);
  825.   CSelect := GetColor($0604);
  826.   CNormDisabled := GetColor($0202);
  827.   CSelDisabled := GetColor($0505);
  828.   MoveChar(B, ' ', Byte(CNormal), Size.X);
  829.   if Menu <> nil then
  830.   begin
  831.     X := 1;
  832.     P := Menu^.Items;
  833.     while P <> nil do
  834.     begin
  835.       if P^.Name <> nil then
  836.       begin
  837.         L := CStrLen(P^.Name^);
  838.         if X + L < Size.X then
  839.         begin
  840.           if P^.Disabled then
  841.             if P = Current then
  842.               Color := CSelDisabled else
  843.               Color := CNormDisabled else
  844.             if P = Current then
  845.               Color := CSelect else
  846.               Color := CNormal;
  847.           MoveChar(B[X], ' ', Byte(Color), 1);
  848.           MoveCStr(B[X + 1], P^.Name^, Color);
  849.           MoveChar(B[X + L + 1], ' ', Byte(Color), 1);
  850.         end;
  851.         Inc(X, L + 2);
  852.       end;
  853.       P := P^.Next;
  854.     end;
  855.   end;
  856.   WriteBuf(0, 0, Size.X, 1, B);
  857. end;
  858.  
  859. procedure TMenuBar.GetItemRect(Item: PMenuItem; var R: TRect);
  860. var
  861.   P: PMenuItem;
  862. begin
  863.   R.Assign(1, 0, 1, 1);
  864.   P := Menu^.Items;
  865.   while True do
  866.   begin
  867.     R.A.X := R.B.X;
  868.     if P^.Name <> nil then Inc(R.B.X, CStrLen(P^.Name^)+2);
  869.     if P = Item then Exit;
  870.     P := P^.Next;
  871.   end;
  872. end;
  873.  
  874. { TMenuBox }
  875.  
  876. constructor TMenuBox.Init(var Bounds: TRect; AMenu: PMenu;
  877.   AParentMenu: PMenuView);
  878. var
  879.   W, H, L: Integer;
  880.   P: PMenuItem;
  881.   R: TRect;
  882. begin
  883.   W := 10;
  884.   H := 2;
  885.   if AMenu <> nil then
  886.   begin
  887.     P := AMenu^.Items;
  888.     while P <> nil do
  889.     begin
  890.       if P^.Name <> nil then
  891.       begin
  892.         L := CStrLen(P^.Name^) + 10;
  893.         if P^.Command = 0 then Inc(L, 3) else
  894.           if P^.Param <> nil then Inc(L, CStrLen(P^.Param^) + 2);
  895.         if L > W then W := L;
  896.       end;
  897.       Inc(H);
  898.       P := P^.Next;
  899.     end;
  900.   end;
  901.   R.Copy(Bounds);
  902.   if R.A.X + W < R.B.X then R.B.X := R.A.X + W else R.A.X := R.B.X - W;
  903.   if R.A.Y + H < R.B.Y then R.B.Y := R.A.Y + H else R.A.Y := R.B.Y - H;
  904.   TMenuView.Init(R);
  905.   State := State or sfShadow;
  906.   Options := Options or ofPreProcess;
  907.   Menu := AMenu;
  908.   ParentMenu := AParentMenu;
  909. end;
  910.  
  911. procedure TMenuBox.Draw;
  912. var
  913.   CNormal, CSelect, CNormDisabled, CSelDisabled, Color: Word;
  914.   Y: Integer;
  915.   P: PMenuItem;
  916.   B: TDrawBuffer;
  917.  
  918. procedure FrameLine(N: Integer);
  919. const
  920.   FrameChars: array[0..19] of Char = '┼┴╟╔╦╚┤ ═┤─═';
  921. begin
  922.   MoveBuf(B[0], FrameChars[N], Byte(CNormal), 1);
  923.   MoveChar(B[1], FrameChars[N + 1], Byte(Color), Size.X - 2);
  924.   MoveBuf(B[Size.X - 1], FrameChars[N + 2], Byte(CNormal), 1);
  925. end;
  926.  
  927. procedure DrawLine;
  928. begin
  929.   WriteBuf(0, Y, Size.X, 1, B);
  930.   Inc(Y);
  931. end;
  932.  
  933. begin
  934.   CNormal := GetColor($0301);
  935.   CSelect := GetColor($0604);
  936.   CNormDisabled := GetColor($0202);
  937.   CSelDisabled := GetColor($0505);
  938.   Y := 0;
  939.   Color := CNormal;
  940.   FrameLine(0);
  941.   DrawLine;
  942.   if Menu <> nil then
  943.   begin
  944.     P := Menu^.Items;
  945.     while P <> nil do
  946.     begin
  947.       Color := CNormal;
  948.       if P^.Name = nil then
  949.       begin
  950.         FrameLine(9);
  951.         MoveChar(B[1], ' ',Byte(Color), 1);
  952.         MoveChar(B[Size.X - 2], ' ',Byte(Color), 1);
  953.       end else
  954.       begin
  955.         if P^.Disabled then
  956.           if P = Current then
  957.             Color := CSelDisabled else
  958.             Color := CNormDisabled else
  959.           if P = Current then Color := CSelect;
  960.         FrameLine(6);
  961.         MoveCStr(B[2], P^.Name^, Color);
  962.         if P^.Command = 0 then
  963.           MoveChar(B[Size.X - 4], #16, Byte(Color), 1) else
  964.           if P^.Param <> nil then
  965.             MoveStr(B[Size.X - 2 - Length(P^.Param^)],
  966.               P^.Param^, Byte(Color));
  967.       end;
  968.       DrawLine;
  969.       P := P^.Next;
  970.     end;
  971.   end;
  972.   Color := CNormal;
  973.   FrameLine(3);
  974.   DrawLine;
  975. end;
  976.  
  977. procedure TMenuBox.GetItemRect(Item: PMenuItem; var R: TRect);
  978. var
  979.   Y: Integer;
  980.   P: PMenuItem;
  981. begin
  982.   Y := 1;
  983.   P := Menu^.Items;
  984.   while P <> Item do
  985.   begin
  986.     Inc(Y);
  987.     P := P^.Next;
  988.   end;
  989.   R.Assign(2, Y, Size.X - 2, Y + 1);
  990. end;
  991.  
  992.  
  993. constructor TMenuPopup.Init(var Bounds: TRect; AMenu: PMenu);
  994. begin
  995.   inherited Init(Bounds, AMenu, nil);
  996.   Origin := Bounds.A;
  997. end;
  998.  
  999. destructor TMenuPopup.Done;
  1000. begin
  1001.   inherited Done;
  1002.   if Menu <> nil then DisposeMenu(Menu);
  1003. end;
  1004.  
  1005. function TMenuPopup.GetPalette: PPalette;
  1006. const
  1007.   P: string[Length(CMenuPopup)] = CMenuPopup;
  1008. begin
  1009.   GetPalette := @P;
  1010. end;
  1011.  
  1012. procedure TMenuPopup.HandleEvent(var Event: TEvent);
  1013. var
  1014.   P: PMenuItem;
  1015. begin
  1016.   case Event.What of
  1017.     evKeyDown:
  1018.       begin
  1019.         P := FindItem(GetCtrlChar(Event.KeyCode));
  1020.         if P = nil then
  1021.           P := HotKey(Event.KeyCode);
  1022.         if (P <> nil) and (CommandEnabled(P^.Command)) then
  1023.         begin
  1024.           Event.What := evCommand;
  1025.           Event.Command := P^.Command;
  1026.           Event.InfoPtr := nil;
  1027.           PutEvent(Event);
  1028.           ClearEvent(Event);
  1029.         end
  1030.         else
  1031.           if GetAltChar(Event.KeyCode) <> #0 then
  1032.             ClearEvent(Event);
  1033.       end;
  1034.   end;
  1035.   inherited HandleEvent(Event);
  1036. end;
  1037.  
  1038. { TStatusLine }
  1039.  
  1040. constructor TStatusLine.Init(var Bounds: TRect; ADefs: PStatusDef);
  1041. begin
  1042.   TView.Init(Bounds);
  1043.   Options := Options or ofPreProcess;
  1044.   EventMask := EventMask or evBroadcast;
  1045.   GrowMode := gfGrowLoY + gfGrowHiX + gfGrowHiY;
  1046.   Defs := ADefs;
  1047.   FindItems;
  1048. end;
  1049.  
  1050. constructor TStatusLine.Load(var S: TStream);
  1051.  
  1052. function DoLoadStatusItems: PStatusItem;
  1053. var
  1054.   Count: Integer;
  1055.   Cur, First: PStatusItem;
  1056.   Last: ^PStatusItem;
  1057. begin
  1058.   Cur := nil;
  1059.   Last := @First;
  1060.   S.Read(Count, SizeOf(Integer));
  1061.   while Count > 0 do
  1062.   begin
  1063.     New(Cur);
  1064.     Last^ := Cur;
  1065.     Last := @Cur^.Next;
  1066.     Cur^.Text := S.ReadStr;
  1067.     S.Read(Cur^.KeyCode, SizeOf(Word) * 2);
  1068.     Dec(Count);
  1069.   end;
  1070.   Last^ := nil;
  1071.   DoLoadStatusItems := First;
  1072. end;
  1073.  
  1074. function DoLoadStatusDefs: PStatusDef;
  1075. var
  1076.   Cur, First: PStatusDef;
  1077.   Last: ^PStatusDef;
  1078.   Count: Integer;
  1079. begin
  1080.   Last := @First;
  1081.   S.Read(Count, SizeOf(Integer));
  1082.   while Count > 0 do
  1083.   begin
  1084.     New(Cur);
  1085.     Last^ := Cur;
  1086.     Last := @Cur^.Next;
  1087.     S.Read(Cur^.Min, 2 * SizeOf(Word));
  1088.     Cur^.Items := DoLoadStatusItems;
  1089.     Dec(Count);
  1090.   end;
  1091.   Last^ := nil;
  1092.   DoLoadStatusDefs := First;
  1093. end;
  1094.  
  1095. begin
  1096.   TView.Load(S);
  1097.   Defs := DoLoadStatusDefs;
  1098.   FindItems;
  1099. end;
  1100.  
  1101. destructor TStatusLine.Done;
  1102. var
  1103.   T: PStatusDef;
  1104.  
  1105. procedure DisposeItems(Item: PStatusItem);
  1106. var
  1107.   T: PStatusItem;
  1108. begin
  1109.   while Item <> nil do
  1110.   begin
  1111.     T := Item;
  1112.     Item := Item^.Next;
  1113.     DisposeStr(T^.Text);
  1114.     Dispose(T);
  1115.   end;
  1116. end;
  1117.  
  1118. begin
  1119.   while Defs <> nil do
  1120.   begin
  1121.     T := Defs;
  1122.     Defs := Defs^.Next;
  1123.     DisposeItems(T^.Items);
  1124.     Dispose(T);
  1125.   end;
  1126.   TView.Done;
  1127. end;
  1128.  
  1129. procedure TStatusLine.Draw;
  1130. begin
  1131.   DrawSelect(nil);
  1132. end;
  1133.  
  1134. procedure TStatusLine.DrawSelect(Selected: PStatusItem);
  1135. var
  1136.   B: TDrawBuffer;
  1137.   T: PStatusItem;
  1138.   I, L: Integer;
  1139.   CSelect, CNormal, CSelDisabled, CNormDisabled: Word;
  1140.   Color: Word;
  1141.   HintBuf: String;
  1142. begin
  1143.   CNormal := GetColor($0301);
  1144.   CSelect := GetColor($0604);
  1145.   CNormDisabled := GetColor($0202);
  1146.   CSelDisabled := GetColor($0505);
  1147.   MoveChar(B, ' ', Byte(CNormal), Size.X);
  1148.   T := Items;
  1149.   I := 0;
  1150.   while T <> nil do
  1151.   begin
  1152.     if T^.Text <> nil then
  1153.     begin
  1154.       L := CStrLen(T^.Text^);
  1155.       if I + L < Size.X then
  1156.       begin
  1157.         if CommandEnabled(T^.Command) then
  1158.           if T = Selected then
  1159.             Color := CSelect else
  1160.             Color := CNormal else
  1161.           if T = Selected then
  1162.             Color := CSelDisabled else
  1163.             Color := CNormDisabled;
  1164.         MoveChar(B[I], ' ', Byte(Color), 1);
  1165.         MoveCStr(B[I + 1], T^.Text^, Color);
  1166.         MoveChar(B[I + L + 1], ' ', Byte(Color), 1);
  1167.       end;
  1168.       Inc(I, L + 2);
  1169.     end;
  1170.     T := T^.Next;
  1171.   end;
  1172.   if I < Size.X - 2 then
  1173.   begin
  1174.     HintBuf := Hint(HelpCtx);
  1175.     if HintBuf <> '' then
  1176.     begin
  1177.       MoveChar(B[I], #179, Byte(CNormal), 1);
  1178.       Inc(I, 2);
  1179.       if I + Length(HintBuf) > Size.X then HintBuf[0] := Char(Size.X - I);
  1180.       MoveStr(B[I], HintBuf, Byte(CNormal));
  1181.     end;
  1182.   end;
  1183.   WriteLine(0, 0, Size.X, 1, B);
  1184. end;
  1185.  
  1186. procedure TStatusLine.FindItems;
  1187. var
  1188.   P: PStatusDef;
  1189. begin
  1190.   P := Defs;
  1191.   while (P <> nil) and ((HelpCtx < P^.Min) or (HelpCtx > P^.Max)) do
  1192.     P := P^.Next;
  1193.   if P = nil then Items := nil else Items := P^.Items;
  1194. end;
  1195.  
  1196. function TStatusLine.GetPalette: PPalette;
  1197. const
  1198.   P: string[Length(CStatusLine)] = CStatusLine;
  1199. begin
  1200.   GetPalette := @P;
  1201. end;
  1202.  
  1203. procedure TStatusLine.HandleEvent(var Event: TEvent);
  1204. var
  1205.   Mouse: TPoint;
  1206.   T: PStatusItem;
  1207.  
  1208. function ItemMouseIsIn: PStatusItem;
  1209. var
  1210.   I,K: Word;
  1211.   T: PStatusItem;
  1212. begin
  1213.   ItemMouseIsIn := nil;
  1214.   if Mouse.Y <> 0 then Exit;
  1215.   I := 0;
  1216.   T := Items;
  1217.   while T <> nil do
  1218.   begin
  1219.     if T^.Text <> nil then
  1220.     begin
  1221.       K := I + CStrLen(T^.Text^) + 2;
  1222.       if (Mouse.X >= I) and (Mouse.X < K) then
  1223.       begin
  1224.         ItemMouseIsIn := T;
  1225.         Exit;
  1226.       end;
  1227.       I := K;
  1228.     end;
  1229.     T := T^.Next;
  1230.   end;
  1231. end;
  1232.  
  1233. begin
  1234.   TView.HandleEvent(Event);
  1235.   case Event.What of
  1236.     evMouseDown:
  1237.       begin
  1238.         T := nil;
  1239.         repeat
  1240.           MakeLocal(Event.Where, Mouse);
  1241.           if T <> ItemMouseIsIn then
  1242.           begin
  1243.             T := ItemMouseIsIn;
  1244.             DrawSelect(T);
  1245.           end;
  1246.         until not MouseEvent(Event, evMouseMove);
  1247.         if (T <> nil) and CommandEnabled(T^.Command) then
  1248.         begin
  1249.           Event.What := evCommand;
  1250.           Event.Command := T^.Command;
  1251.           Event.InfoPtr := nil;
  1252.           PutEvent(Event);
  1253.         end;
  1254.         ClearEvent(Event);
  1255.         DrawView;
  1256.       end;
  1257.     evKeyDown:
  1258.       begin
  1259.         T := Items;
  1260.         while T <> nil do
  1261.         begin
  1262.           if (Event.KeyCode = T^.KeyCode) and
  1263.             CommandEnabled(T^.Command) then
  1264.           begin
  1265.             Event.What := evCommand;
  1266.             Event.Command := T^.Command;
  1267.             Event.InfoPtr := nil;
  1268.             Exit;
  1269.           end;
  1270.           T := T^.Next;
  1271.         end;
  1272.       end;
  1273.     evBroadcast:
  1274.       if Event.Command = cmCommandSetChanged then DrawView;
  1275.   end;
  1276. end;
  1277.  
  1278. function TStatusLine.Hint(AHelpCtx: Word): String;
  1279. begin
  1280.   Hint := '';
  1281. end;
  1282.  
  1283. procedure TStatusLine.Store(var S: TStream);
  1284.  
  1285. procedure DoStoreStatusItems(Cur: PStatusItem);
  1286. var
  1287.   T: PStatusItem;
  1288.   Count: Integer;
  1289. begin
  1290.   Count := 0;
  1291.   T := Cur;
  1292.   while T <> nil do
  1293.   begin
  1294.     Inc(Count);
  1295.     T := T^.Next
  1296.   end;
  1297.   S.Write(Count, SizeOf(Integer));
  1298.   while Cur <> nil do
  1299.   begin
  1300.     S.WriteStr(Cur^.Text);
  1301.     S.Write(Cur^.KeyCode, SizeOf(Word) * 2);
  1302.     Cur := Cur^.Next;
  1303.   end;
  1304. end;
  1305.  
  1306. procedure DoStoreStatusDefs(Cur: PStatusDef);
  1307. var
  1308.   Count: Integer;
  1309.   T: PStatusDef;
  1310. begin
  1311.   Count := 0;
  1312.   T := Cur;
  1313.   while T <> nil do
  1314.   begin
  1315.     Inc(Count);
  1316.     T := T^.Next
  1317.   end;
  1318.   S.Write(Count, SizeOf(Integer));
  1319.   while Cur <> nil do
  1320.   begin
  1321.     with Cur^ do
  1322.     begin
  1323.       S.Write(Min, SizeOf(Word) * 2);
  1324.       DoStoreStatusItems(Items);
  1325.     end;
  1326.     Cur := Cur^.Next;
  1327.   end;
  1328. end;
  1329.  
  1330. begin
  1331.   TView.Store(S);
  1332.   DoStoreStatusDefs(Defs);
  1333. end;
  1334.  
  1335. procedure TStatusLine.Update;
  1336. var
  1337.   H: Word;
  1338.   P: PView;
  1339. begin
  1340.   P := TopView;
  1341.   if P <> nil then
  1342.     H := P^.GetHelpCtx else
  1343.     H := hcNoContext;
  1344.   if HelpCtx <> H then
  1345.   begin
  1346.     HelpCtx := H;
  1347.     FindItems;
  1348.     DrawView;
  1349.   end;
  1350. end;
  1351.  
  1352. function NewStatusDef(AMin, AMax: Word; AItems: PStatusItem;
  1353.   ANext:PStatusDef): PStatusDef;
  1354. var
  1355.   T: PStatusDef;
  1356. begin
  1357.   New(T);
  1358.   with T^ do
  1359.   begin
  1360.     Next := ANext;
  1361.     Min := AMin;
  1362.     Max := AMax;
  1363.     Items := AItems;
  1364.   end;
  1365.   NewStatusDef := T;
  1366. end;
  1367.  
  1368. function NewStatusKey(const AText: String; AKeyCode: Word; ACommand: Word;
  1369.   ANext: PStatusItem): PStatusItem;
  1370. var
  1371.   T: PStatusItem;
  1372. begin
  1373.   New(T);
  1374.   T^.Text := NewStr(AText);
  1375.   T^.KeyCode := AKeyCode;
  1376.   T^.Command := ACommand;
  1377.   T^.Next := ANext;
  1378.   NewStatusKey := T;
  1379. end;
  1380.  
  1381. procedure RegisterMenus;
  1382. begin
  1383.   RegisterType(RMenuBar);
  1384.   RegisterType(RMenuBox);
  1385.   RegisterType(RStatusLine);
  1386.   RegisterType(RMenuPopup);
  1387. end;
  1388.  
  1389. { StatusLine messaging }
  1390.  
  1391. procedure StatusMessage(Message: string);
  1392. var
  1393.   C: Byte;
  1394.   B: TDrawBuffer;
  1395. begin
  1396.   MPointerState := 1;
  1397.   HideMouse;
  1398.   ShowMouse;
  1399.   with StatusLine^ do
  1400.   begin
  1401.     C := GetColor(1);
  1402.     MoveChar(B, ' ', C, Size.X);
  1403.     MoveStr(B, ' ' + Message + '.', C);
  1404.     WriteLine(0, 0, Size.X, 1, B);
  1405.   end;
  1406. end;
  1407.  
  1408. procedure ClearStatusMessage;
  1409. begin
  1410.   MPointerState := 0;
  1411.   HideMouse;
  1412.   ShowMouse;
  1413.   StatusLine^.Draw;
  1414. end;
  1415.  
  1416. end.
  1417.