-- Compiler menu/nb
-- MStone November 26, 1980  5:50 PM
-- Tiberi January 25, 1980  1:37 PM

DIRECTORY
	MenuDefs: FROM "MenuDefs",
	ControllerDefs: FROM "ControllerDefs",
	StringDefs: FROM "StringDefs",
	ScreenDefs: FROM "ScreenDefs",
	StyleDefs: FROM "StyleDefs",
	RefreshDefs: FROM "RefreshDefs",
	GriffinFontDefs: FROM "GriffinFontDefs",
	PointDefs: FROM "PointDefs",
	ObjectDefs: FROM "ObjectDefs"
		USING [MenuOrientation, StartObject, ObjectProc,
		ForAllObjects, ReplotBoxFromObject],
	GriffinMemoryDefs: FROM "GriffinMemoryDefs";

Menu: PROGRAM
	IMPORTS StringDefs, ObjectDefs, GriffinFontDefs,
		GriffinMemoryDefs, ScreenDefs, ControllerDefs,
		RefreshDefs, PointDefs
	EXPORTS MenuDefs SHARES MenuDefs = 
BEGIN OPEN MenuDefs, ObjectDefs;

X: INTEGER = PointDefs.X;
Y: INTEGER = PointDefs.Y;

menuLineWidth: INTEGER = 3;
menuMargin: INTEGER = 4;
menuTopMargin: INTEGER = 1;
menuStyle: StyleDefs.StyleHandle ← NIL;
menuFont: GriffinFontDefs.FontDescriptor;
menuHeight: INTEGER ← 0;
menuBaseLine: INTEGER ← 0;
menuGrey: POINTER TO ARRAY [0..3] OF CARDINAL ← NIL;
black: POINTER TO ARRAY [0..3] OF CARDINAL ← NIL;


twoLineWidth: INTEGER = 2*menuLineWidth;

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

CreateMenu: PUBLIC PROCEDURE [orientation: MenuOrientation, tl: PointDefs.ScrPt, title: STRING] RETURNS [menu: MenuHandle] = 
BEGIN
menu ← LOOPHOLE [StartObject [menu], MenuHandle];
-- a MenuHandle is a POINTER TO 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 title.length > 0
   THEN AddMenuItem [menu, title, NullMenuProc].inverted ← TRUE;
END;

NullMenuProc: MenuProc = BEGIN END;

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

AddMenuItem: PUBLIC PROCEDURE [menu: MenuHandle, string: STRING, proc: MenuProc] RETURNS [MenuItemHandle] = 
BEGIN
newitem: MenuItemHandle;
rover: MenuItemHandle;
first: BOOLEAN ← FALSE;

newitem ← GriffinMemoryDefs.Allocate [SIZE [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
ELSE	BEGIN
	FOR rover ← 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 ← GriffinMemoryDefs.AllocateString [string.length];
StringDefs.AppendString [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, ItemWidth[string]]
		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 object: obj SELECT FROM
		menu => proc[@object];
	ENDCASE; 
	END;
ForAllObjects[IsMenu];
END;

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

PlotMenu:  PUBLIC PROCEDURE [menu: MenuHandle] = 
BEGIN
tl, br: PointDefs.ScrPt;
ScreenDefs.EraseBox[menu.tl, menu.br];
ScreenDefs.SetFunction[replace];
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];

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

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

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

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: PUBLIC MenuProc = 
BEGIN
tl, br: PointDefs.ScrPt;
screen: ScreenDefs.ScreenPtr ← ScreenDefs.GetCurrentScreen[];
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 tl[X] >screen.rx OR tl[Y] >screen.by
   OR br[X] < screen.lx OR br[Y] < screen.ty
	THEN RETURN;

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[replace];
ScreenDefs.SetFillParms[menuGrey];
ScreenDefs.BoxFill [tl, br];

GriffinFontDefs.DisplayString [item.string,
	MenuAnchorPoint[item],
	StyleDefs.Anchor [center],
	StyleDefs.Orientation [or0], @menuFont];
IF item.inverted THEN
	BEGIN
	ScreenDefs.InvertBox[item.tl, item.br];
	END;
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 [STRING] =
BEGIN RETURN [item.string] END;
-- the string returned might go away sometime!

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: STRING] =
BEGIN
menu: MenuHandle = item.menu;
width: INTEGER ← 0;
FindWidest: MenuProc =
	BEGIN
	w: INTEGER ← ItemWidth[item.string];
	IF w>width THEN width ← w;
	END;
GriffinMemoryDefs.FreeString[item.string];
item.string ← GriffinMemoryDefs.AllocateString [string.length];
StringDefs.AppendString [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];
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];
END;

InitMenuStyle: PROCEDURE =
BEGIN OPEN GriffinFontDefs, StringDefs;
currentFont: FontDescriptorHandle;
best: FontDescriptorHandle ← NIL;
nameInSet: PROC[s: STRING] RETURNS[BOOLEAN] = {
	RETURN[ 
	EquivalentString[s, "Cream"] OR
	EquivalentString[s, "Helvetica"] OR
	EquivalentString[s, "TimesRoman"] OR
	EquivalentString[s, "Gacha"] OR
	EquivalentString[s, "SmallTalk"]];
	};

findCream10: PROC[font: FontDescriptorHandle] = {
	IF font.rotation=Rot0Degrees AND
		EquivalentString[font.name, "Cream"] AND font.points=10 
		THEN best ← font;
	};
findAny10InSet: PROC[font: FontDescriptorHandle] = {
	IF font.rotation=Rot0Degrees AND
		font.points=10 AND nameInSet[font.name] THEN best ← font;
	};

findAnyInSet: PROC[font: FontDescriptorHandle] = {
	IF font.rotation=Rot0Degrees AND nameInSet[font.name]
		THEN best ← font;
	};

findAny: PROC[font: FontDescriptorHandle] = {
	IF font.rotation=Rot0Degrees AND best#NIL THEN best ← font;
	};

menuStyle ← ControllerDefs.CurrentStyleRecord[];
--pick a menu font.  Try for Cream 10, then one of the nameset above
--Then, find the first font that meets one of those names
--default to the first font found
ForAllFonts[findCream10];
IF best=NIL THEN ForAllFonts[findAny10InSet];
IF best=NIL THEN ForAllFonts[findAnyInSet];
IF best=NIL THEN ForAllFonts[findAny];
IF best=NIL THEN StartupFontError[nofonts];
currentFont ← ControllerDefs.CurrentFontDescriptor[];
currentFont↑ ← best↑;
	BEGIN OPEN menuStyle;
	color ← [0,0,128]; -- 50% grey
	fillcolor ← [0,0,128];
	backgndcolor ← [0,0,128];
	anchor ← center;
	orientation ← or0;
	END;
menuStyle ← ControllerDefs.CurrentStyle[];
menuFont ← ControllerDefs.FontWithNumber[menuStyle.fontid];
menuHeight ← PointDefs.ObjValToScrVal[GriffinFontDefs.MaxHeight
		[@menuFont]] + menuTopMargin-1;
menuBaseLine ← GriffinFontDefs.BaseLine [@menuFont];
menuGrey ← ControllerDefs.GreyOfColor[menuStyle.color];
black ← ControllerDefs.GreyOfColor[[0,0,0]];
END;

InitMenuStyle;

END.