home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 9 / 09.iso / l / l040 / 13.ddi / RTLTV.ZIP / MENUS.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1992-10-28  |  30.2 KB  |  1,361 lines

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