home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / vp21beta.zip / ATVSRC.RAR / MENUS.PAS < prev    next >
Pascal/Delphi Source File  |  2000-08-15  |  31KB  |  1,369 lines

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