-- Compiler menu/nb
-- MStone November 26, 1980 5:50 PM
-- Tiberi January 25, 1980 1:37 PM
-- MStone February 15, 1984 11:24:17 am PST

DIRECTORY
 MenuDefs: FROM "MenuDefs",
 Graphics USING [Context],
 ControllerDefs USING [AppendFont, CurrentStyle],
 Rope USING [Length,ROPE],
 ScreenDefs USING [SetFunction, SetFillParms, BoxFill, InvertBox, GreyType, EraseBox],
 ScreenDefsExtras USING [ShowMenuString],
 StyleDefs: FROM "StyleDefs",
 RefreshDefs: FROM "RefreshDefs",
 GriffinFontDefs: FROM "GriffinFontDefs",
 PointDefs: FROM "PointDefs",
 GraphicsColor USING [IntensityToColor],
 ObjectDefs: FROM "ObjectDefs"
  USING [MenuOrientation, StartObject, ObjectProc,
  ForAllObjects, ReplotBoxFromObject, Object],
 GriffinMemoryDefs USING [CZone];

GriffinMenu: PROGRAM
IMPORTS Rope, ObjectDefs, GriffinFontDefs,
  GriffinMemoryDefs, ScreenDefs, ControllerDefs,
  RefreshDefs, GraphicsColor, PointDefs, ScreenDefsExtras
EXPORTS MenuDefs =
BEGIN OPEN MenuDefs, ObjectDefs, GriffinMemoryDefs;

X: INTEGER = PointDefs.X;
Y: INTEGER = PointDefs.Y;
ROPE: TYPE = Rope.ROPE;

menuLineWidth: INTEGER = 3;
menuMargin: INTEGER = 4;
menuTopMargin: INTEGER = 1;
menuStyle: StyleDefs.StyleHandle ← NIL;
menuFont: GriffinFontDefs.FontDescriptorHandle ← CZone.NEW[GriffinFontDefs.FontDescriptor ←
 [name: "Cream", rotation: 0, face: 0, points: 10]];
menuHeight: INTEGER ← 0;
menuBaseLine: REAL ← 0;
menuGrey: ScreenDefs.GreyType ← GraphicsColor.IntensityToColor[.5];


twoLineWidth: INTEGER = 2*menuLineWidth;

MenuStyle: PUBLIC PROCEDURE RETURNS [StyleDefs.StyleHandle] =
BEGIN
RETURN [menuStyle]
END;

CreateMenu: PUBLIC PROCEDURE [orientation: MenuOrientation, tl: PointDefs.ScrPt, title: ROPE] RETURNS [menu: MenuHandle] =
BEGIN
menu ← NARROW [StartObject [menu]];
-- a MenuHandle is a REF menu Object
menu.head ← NIL;
menu.validEncoding ← TRUE;
menu.style ← menuStyle;
menu.orientation ← orientation;
menu.visible ← FALSE;
menu.tl ← tl; -- leave space for enclosing box
SELECT orientation FROM
vertical =>
BEGIN
 menu.br [X] ← tl [X] + twoLineWidth-1;
 menu.br [Y] ← tl [Y] + menuLineWidth-1;
END;
horizontal =>
BEGIN
 menu.br [X] ← tl [X] + menuLineWidth-1;
 menu.br [Y] ← tl [Y] + twoLineWidth + menuHeight;
END;
ENDCASE;
IF title # NIL AND Rope.Length[title] > 0
THEN AddMenuItem [menu, title, NullMenuProc].inverted ← TRUE;
END;

NullMenuProc: MenuProc = BEGIN END;

ItemWidth: PROCEDURE [string:ROPE] RETURNS [INTEGER] = INLINE
BEGIN
RETURN [
 PointDefs.ObjValToScrVal[GriffinFontDefs.StringWidth[string,menuFont, menuStyle.orientation]] + menuMargin - 1];
END;

AddMenuItem: PUBLIC PROCEDURE [menu: MenuHandle, string: ROPE, proc: MenuProc] RETURNS [MenuItemHandle] =
BEGIN
newitem: MenuItemHandle;
rover: MenuItemHandle;
first: BOOLEANFALSE;

newitem ← CZone.NEW[MenuItem];
newitem^ ← [link: NIL, menu: menu, selected: FALSE, inverted: FALSE,
 tl: , br: , string: NIL, proc: proc];
IF menu.head=NIL
THEN BEGIN first ← TRUE; menu.head ← newitem END
ELSEBEGIN
FOR rover ← NARROW[menu.head], rover.link UNTIL rover.link = NIL
  DO ENDLOOP; --find end
 rover.link ← newitem
END;
SELECT menu.orientation FROM
vertical =>
BEGIN
 newitem.tl [X] ← menu.tl [X] + menuLineWidth;
 newitem.tl [Y] ← menu.br [Y] + 1;
END;
horizontal =>
BEGIN
 newitem.tl [X] ← menu.br [X] + 1;
 newitem.tl [Y] ← menu.tl [Y] + menuLineWidth;
END;
ENDCASE;
newitem.string ← string;
SELECT menu.orientation FROM
vertical =>
BEGIN
 width: INTEGER ← ItemWidth[string];
IF first OR menu.tl[X] + width + twoLineWidth > menu.br [X]
  THEN ChangeMenuWidth[menu, width]
  ELSE newitem.br [X] ← menu.br [X] - menuLineWidth;
 newitem.br [Y] ← newitem.tl [Y] + menuHeight;
 menu.br [Y] ← newitem.br [Y] + menuLineWidth;
END;
horizontal =>
BEGIN
 newitem.br [X] ← newitem.tl [X] + ItemWidth[string];
 newitem.br [Y] ← menu.br [Y] - menuLineWidth;
 menu.br [X] ← newitem.br [X] + menuLineWidth
END;
ENDCASE;
IF menu.visible THEN RefreshDefs.EraseAndSave[menu];
RETURN [newitem];
END;

ChangeMenuWidth: PROCEDURE [menu: MenuHandle, width: INTEGER] =
BEGIN OPEN ObjectDefs;
WidenMenu: MenuProc =
BEGIN
 item.br [X] ← item.tl [X] + width;
END;
IF menu.orientation # vertical THEN ERROR;
ForAllMenuItems [menu, WidenMenu];
menu.br [X] ← menu.tl [X] + width + twoLineWidth;
IF menu.visible THEN RefreshDefs.EraseAndSave[menu];
END;

ForAllMenus: PUBLIC PROCEDURE [proc: PROCEDURE [menu: MenuHandle]] =
BEGIN OPEN ObjectDefs;
IsMenu: ObjectProc =
BEGIN
WITH obj SELECT FROM
  mref: REF Object[menu] => proc[mref];
ENDCASE;
END;
ForAllObjects[IsMenu];
END;

ForAllMenuItems: PUBLIC PROCEDURE [menu: MenuHandle, proc: MenuProc] =
BEGIN
rover: MenuItemHandle;
FOR rover ← NARROW[menu.head], rover.link UNTIL rover = NIL
  DO proc [rover] ENDLOOP;
END;

PlotMenu: PUBLIC PROCEDURE [menu: MenuHandle, dc: Graphics.Context] =
BEGIN
tl, br: PointDefs.ScrPt;
showItem: MenuProc = {ShowItem[item,dc]};
ScreenDefs.EraseBox[menu.tl, menu.br, dc];
ScreenDefs.SetFunction[opaque];
ScreenDefs.SetFillParms[menuGrey];
menu.visible ← TRUE;

--top edge:
tl ← menu.tl;
br [X] ← menu.br[X];
br [Y] ← tl [Y] + menuLineWidth - 1;
ScreenDefs.BoxFill [tl, br, dc];

--left edge:
br [X] ← tl [X] + menuLineWidth - 1;
br [Y] ← menu.br [Y];
tl ← menu.tl;
ScreenDefs.BoxFill [tl, br, dc];

-- the right edge:
tl [X] ← menu.br [X] - menuLineWidth + 1;
tl [Y] ← menu.tl [Y];
br ← menu.br;
ScreenDefs.BoxFill [tl, br, dc];

-- the bottom edge:
tl [X] ← menu.tl [X];
tl [Y] ← menu.br [Y] - menuLineWidth + 1;
br ← menu.br;
ScreenDefs.BoxFill [tl, br, dc];

ForAllMenuItems [menu, showItem];
END;

ShowMenu: PUBLIC PROCEDURE [menu: MenuHandle] =
BEGIN
menu.visible ← TRUE;
RefreshDefs.PlotAndMark[menu];
END;

HideMenu: PUBLIC PROCEDURE [menu: MenuHandle] =
BEGIN
menu.visible ← FALSE;
RefreshDefs.EraseAndSave[menu];
END;

ShowItem: PROCEDURE [item: MenuItemHandle, dc: Graphics.Context] =
BEGIN
tl, br: PointDefs.ScrPt;
tl ← item.tl;
br ← IF item.menu.orientation = vertical
THEN [item.br[X], item.br[Y] + menuLineWidth]
ELSE [item.br[X] + menuLineWidth, item.br[Y]];

IF item.menu.orientation = vertical
THEN tl [Y] ← item.br [Y]+1 -- bottom edges
ELSE tl [X] ← item.br [X]+1; -- right edges
ScreenDefs.SetFunction[opaque];
ScreenDefs.SetFillParms[menuGrey];
ScreenDefs.BoxFill [tl, br, dc];
ScreenDefsExtras.ShowMenuString[MenuAnchorPoint[item], item.string, dc];
-- GriffinFontDefs.DisplayString [item.string,
-- PointDefs.ScrToObj[MenuAnchorPoint[item]],
-- StyleDefs.Anchor [center],
-- StyleDefs.Orientation [or0], menuFont, dc];
IF item.inverted THEN ScreenDefs.InvertBox[item.tl, item.br, dc];
END;

OverWhichItem: PUBLIC PROCEDURE [pt: PointDefs.ScrPt] RETURNS [lastitem: MenuItemHandle] =
BEGIN
lastover: MenuHandle ← NIL;
IsOverMenu: PROCEDURE[m: MenuHandle] =
BEGIN
IF m.visible
AND pt [X] IN [m.tl[X]..m.br[X]]
AND pt [Y] IN [m.tl[Y]..m.br[Y]]
  THEN lastover ← m;
END;
LastItemOver: MenuProc =
BEGIN
IF pt [X] IN [item.tl [X] .. item.br [X]] AND
  pt [Y] IN [item.tl [Y] .. item.br [Y]] THEN lastitem ← item
END;
lastitem ← NIL;
ForAllMenus[IsOverMenu];
IF lastover = NIL THEN RETURN [NIL];
-- which menu item?
ForAllMenuItems[lastover, LastItemOver];
END;

IsOverItem: PUBLIC PROCEDURE [pt: PointDefs.ScrPt,
  item: MenuItemHandle] RETURNS [BOOLEAN] =
BEGIN
IF pt [X] IN [item.tl [X]..item.br [X]]
AND pt [Y] IN [item.tl [Y]..item.br [Y]]
THEN RETURN [TRUE]
ELSE RETURN [FALSE];
END;

BugItem: PUBLIC PROCEDURE [item: MenuItemHandle] =
BEGIN item.proc [item] END;

--questions about menus:

WhichMenu: PUBLIC PROCEDURE [item: MenuItemHandle]
RETURNS [menu: MenuHandle] =
BEGIN RETURN [item.menu] END;

IsSelected: PUBLIC PROCEDURE [item: MenuItemHandle] RETURNS [BOOLEAN] =
BEGIN RETURN [item.selected] END;

MenuString: PUBLIC PROCEDURE [item: MenuItemHandle] RETURNS [ROPE] =
BEGIN RETURN [item.string] END;

MenuAnchorPoint: PUBLIC PROCEDURE [item: MenuItemHandle]
  RETURNS [pt: PointDefs.ScrPt] =
BEGIN
pt[X] ← (item.br[X]+item.tl[X])/2 + 1;
pt[Y] ← item.tl[Y] + menuTopMargin;
END;

--changing things:

SetMenuString: PUBLIC PROCEDURE [item: MenuItemHandle, string: ROPE] =
BEGIN
menu: MenuHandle = item.menu;
width: INTEGER ← 0;
FindWidest: MenuProc =
BEGIN
 w: INTEGER ← ItemWidth[item.string];
IF w>width THEN width ← w;
END;
item.string ← string;
IF menu.visible THEN RefreshDefs.EraseAndSave[menu];
ForAllMenuItems [menu, FindWidest];
ChangeMenuWidth[menu, width];
IF menu.visible THEN RefreshDefs.EraseAndSave[menu];
END;

SelectOnly: PUBLIC MenuProc =
BEGIN
ForAllMenuItems[item.menu, Deselect];
Select[item];
END;

Select: PUBLIC MenuProc =
BEGIN
item.selected ← TRUE;
ClearMenuItem[item]
END;

Deselect: PUBLIC MenuProc =
BEGIN
IF item.selected THEN
BEGIN
 item.selected ← FALSE;
 ClearMenuItem[item];
END;
END;

HighlightMenuItem: PUBLIC MenuProc =
BEGIN
wasInverted: BOOLEAN = item.inverted;
item.inverted ← ~ item.selected;
IF item.menu.visible AND wasInverted # item.inverted
THEN ObjectDefs.ReplotBoxFromObject[item.tl, item.br, item.menu, NIL];
END;

ClearMenuItem: PUBLIC MenuProc =
BEGIN
wasInverted: BOOLEAN = item.inverted;
item.inverted ← item.selected;
IF item.menu.visible AND wasInverted # item.inverted
THEN ObjectDefs.ReplotBoxFromObject[item.tl, item.br, item.menu, NIL];
END;

--called early on, right after the Controller initialization
InitMenuStyle: PUBLIC PROCEDURE =
BEGIN OPEN GriffinFontDefs;
[] ← ControllerDefs.AppendFont[menuFont]; --adds the font and makes it current
menuStyle ← CZone.NEW[StyleDefs.Style];
menuStyle^ ← ControllerDefs.CurrentStyle[]^; --includes current font 
menuStyle.color ← [0,0,128]; -- 50% grey
menuStyle.fillcolor ← [0,0,128];
menuStyle.backgndcolor ← [0,0,128];
menuStyle.anchor ← center;
menuStyle.orientation ← or0;
menuHeight ← PointDefs.ObjValToScrVal[GriffinFontDefs.MaxHeight[menuFont]] + menuTopMargin-1;
menuBaseLine ← GriffinFontDefs.MaxPosExtent[menuFont];
END;

END.