MenusImpl.mesa; Edited by McGregor on October 26, 1982 2:31 pm
Last Edited by: Maxwell, May 24, 1983 7:48 am
DIRECTORY
Graphics
USING [black, Context, DrawBox, DrawRope, FontBox, FontRef, SetColor, SetCP,
SetPaintMode, SetStipple, white],
GraphicsOps USING [DrawTexturedBox, Texture],
Menus,
MenusPrivate,
MessageWindow USING [Append],
Process USING [Detach, Milliseconds, MsecToTicks, priorityNormal, SetPriority, SetTimeout],
Real USING [RoundC],
Rope USING [Equal, FromRefText, ROPE],
SafeStorage USING [NewZone],
TIPUser USING [TIPScreenCoords],
UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc],
VFonts USING [defaultFont, Font, GraphicsFont, StringWidth],
ViewerClasses,
ViewerOps USING [InvertForMenus, PaintEverything, PaintViewer],
ViewerSpecs USING [menuHeight],
WindowManager USING [RestoreCursor],
WindowManagerPrivate USING [DrawCaptionMenu, windowMenu];
MenusImpl: CEDAR MONITOR LOCKS entry USING entry: MenuEntry
IMPORTS Graphics, GraphicsOps, Menus, MessageWindow, Process, Real, Rope, SafeStorage,
UserProfile, VFonts, ViewerOps, WindowManager, WindowManagerPrivate
EXPORTS Menus, MenusPrivate
SHARES Menus, ViewerOps =
BEGIN OPEN ViewerClasses, Menus, MenusPrivate;
menuVFont: VFonts.Font ← VFonts.defaultFont;
menuGFont: Graphics.FontRef ← VFonts.GraphicsFont[menuVFont];
fontHeight: INTEGER;
baselineOffset: INTEGER;
widthFudge: INTEGER = 3;
mZ: ZONE ← SafeStorage.NewZone[quantized];
CopyMenu:
PUBLIC
PROC [old: Menu]
RETURNS [new: Menu] =
BEGIN
new ← CreateMenu[old.linesUsed];
FOR l: MenuLine
IN MenuLine
UNTIL old.lines[l]=
NIL
DO
new.lines[l] ← old.lines[l];
CopyLine[new, l];
ENDLOOP;
END;
CopyLine:
PROC [menu: Menu, line:
INTEGER] =
BEGIN
oldEntry: MenuEntry ← menu.lines[line];
newEntry: MenuEntry;
menu.lines[line] ← NIL;
FOR t: MenuEntry ← oldEntry, t.link
UNTIL t=
NIL
DO
IF menu.lines[line]=NIL THEN newEntry ← menu.lines[line]←mZ.NEW[MenuEntryRec ← t^]
ELSE newEntry ← newEntry.link ← mZ.NEW[MenuEntryRec ← t^];
newEntry.greyed ← FALSE;
newEntry.guardState ← IF newEntry.guarded THEN guarded ELSE armed;
ENDLOOP;
END;
CreateEntry:
PUBLIC
PROC [name: Rope.
ROPE, proc: ClickProc, clientData:
REF
ANY ←
NIL,
documentation: REF ANY ← NIL, fork: BOOL ← TRUE, guarded: BOOL ← FALSE]
RETURNS [entry: MenuEntry] = BEGIN
entry ← mZ.
NEW[MenuEntryRec ← [
name: name,
proc: proc,
clientData: clientData,
documentation: documentation,
fork: fork,
guarded: guarded,
guardState: IF guarded THEN guarded ELSE armed,
width: VFonts.StringWidth[name, menuVFont]
]];
END;
FindEntry:
PUBLIC
PROC [menu: Menu, entryName: Rope.
ROPE]
RETURNS [entry: MenuEntry] =
BEGIN
IF menu=NIL THEN RETURN[NIL];
FOR l: MenuLine
IN MenuLine
UNTIL menu.lines[l]=
NIL
DO
FOR e: MenuEntry ← menu.lines[l], e.link
UNTIL e=
NIL
DO
IF Rope.Equal[e.name, entryName] THEN RETURN[e];
ENDLOOP;
ENDLOOP;
END;
InsertMenuEntry:
PUBLIC
PROC [menu: Menu, entry: MenuEntry, line: MenuLine ← 0] =
BEGIN
entry.link ← menu.lines[line];
menu.lines[line] ← entry;
entry.xPos ← menuHLeading;
FOR e: MenuEntry ← menu.lines[line].link, e.link
UNTIL e=
NIL
DO
e.xPos ← e.xPos + entry.width + menuHSpace;
ENDLOOP;
END;
AppendMenuEntry:
PUBLIC
PROC [menu: Menu, entry: MenuEntry, line: MenuLine ← 0] =
BEGIN
entry.link ← NIL;
IF menu.lines[line]=NIL THEN {entry.xPos ← menuHLeading; menu.lines[line] ← entry}
ELSE
FOR e: MenuEntry ← menu.lines[line], e.link
UNTIL e.link=
NIL
DO
REPEAT FINISHED => {entry.xPos ← e.xPos+e.width+menuHSpace; e.link ← entry};
ENDLOOP;
END;
targetNotFound: PUBLIC SIGNAL = CODE;
ReplaceMenuEntry:
PUBLIC
PROC [menu: Menu, oldEntry: MenuEntry,
newEntry: MenuEntry ← NIL] = BEGIN
l: MenuLine;
x: INTEGER ← menuHLeading;
exit: BOOL ← FALSE;
FOR l
IN MenuLine
UNTIL menu.lines[l]=
NIL
DO
IF oldEntry=menu.lines[l]
THEN
BEGIN
IF newEntry=NIL THEN menu.lines[l] ← menu.lines[l].link
ELSE
BEGIN
newEntry.link ← oldEntry.link;
menu.lines[l] ← newEntry;
END;
EXIT;
END
ELSE
FOR e: MenuEntry ← menu.lines[l], e.link
UNTIL e.link=
NIL
DO
IF e.link=oldEntry
THEN
BEGIN
IF newEntry=NIL THEN e.link ← e.link.link
ELSE
BEGIN
newEntry.link ← oldEntry.link;
e.link ← newEntry;
END;
exit ← TRUE; EXIT;
END;
ENDLOOP;
IF exit THEN EXIT;
REPEAT FINISHED => SIGNAL targetNotFound;
ENDLOOP;
menu.lines[l].xPos ← menuHLeading;
FOR e: MenuEntry ← menu.lines[l], e.link
UNTIL e=
NIL
DO
e.xPos ← x;
x ← x+e.width+menuHSpace;
ENDLOOP;
END;
MenuRow:
PROC [menu: Menu, viewer: ViewerClasses.Viewer, y:
INTEGER]
RETURNS [INTEGER] = INLINE BEGIN
RETURN[
IF menu.linesUsed=1
THEN 0
ELSE
(menu.y+ViewerSpecs.menuHeight-y-viewer.wy)/ViewerSpecs.menuHeight];
END;
MarkMenu:
PUBLIC
PROC [menu: Menus.Menu, parent: ViewerClasses.Viewer,
mousePos: TIPUser.TIPScreenCoords] = BEGIN
row: INTEGER ← MenuRow[menu, parent, mousePos.mouseY];
newEntry: MenuEntry ← ResolveEntry[menu, row, mousePos.mouseX];
IF newEntry=
NIL
THEN
BEGIN
IF menu.inverted#NIL THEN InvertEntry[menu, parent];
menu.inverted ← NIL;
END
ELSE
IF newEntry#menu.inverted
THEN
BEGIN
IF menu.inverted#NIL THEN InvertEntry[menu, parent];
menu.inverted ← newEntry;
menu.lineInverted ← row;
InvertEntry[menu, parent];
END;
END;
HitMenu:
PUBLIC
PROC [menu: Menus.Menu, parent: ViewerClasses.Viewer,
mousePos: TIPUser.TIPScreenCoords, button: MouseButton, shift, control: BOOL ← FALSE] = BEGIN
row: INTEGER ← MenuRow[menu, parent, mousePos.mouseY];
entry: MenuEntry ← ResolveEntry[menu, row, mousePos.mouseX];
hit: BOOL ← menu.inverted#NIL AND entry=menu.inverted;
IF menu.inverted#NIL THEN InvertEntry[menu, parent]; -- take down
menu.inverted ← NIL;
IF hit THEN ProcessMenuHit[entry, menu, parent, button, shift, control];
END;
ProcessMenuHit:
ENTRY
PROC [entry: MenuEntry, menu: Menus.Menu,
parent: ViewerClasses.Viewer, button: MouseButton, shift, control: BOOL] = TRUSTED BEGIN
SELECT entry.guardState
FROM
guarded =>
BEGIN
entry.guardState ← arming;
Process.Detach[FORK ArmMenuProc[entry, menu, parent]];
IF entry.documentation#
NIL
THEN Process.Detach[
FORK Document[entry.documentation, parent, entry.clientData, button, shift, control]];
END;
arming=> NULL; -- no action
armed =>
BEGIN
IF entry.guarded THEN entry.guardState ← guarded;
IF entry.fork
THEN
Process.Detach[FORK MenuPusher[entry, menu, parent, button, shift, control]]
ELSE MenuPusher[entry, menu, parent, button, shift, control, FALSE];
END;
ENDCASE;
END;
ArmMenuProc:
ENTRY
PROC [entry: MenuEntry, menu: Menus.Menu,
parent: ViewerClasses.Viewer] = BEGIN
menuWaitCondition: CONDITION;
assert: state=arming
TRUSTED {Process.SetTimeout[@menuWaitCondition, Process.MsecToTicks[armingTime]]};
WAIT menuWaitCondition;
IF entry.guardState = arming
THEN
BEGIN
entry.guardState ← armed;
RedrawMenu[parent, menu, entry];
TRUSTED {Process.SetTimeout[@menuWaitCondition, Process.MsecToTicks[armedTime]]};
WAIT menuWaitCondition;
END;
IF entry.guardState#guarded
THEN
BEGIN
entry.guardState ← guarded;
RedrawMenu[parent, menu, entry];
END;
END;
MenuPusher:
PROC [entry: MenuEntry, menu: Menu, parent: ViewerClasses.Viewer,
button: MouseButton, shift, control: BOOL ← FALSE, normalPriority: BOOL ← TRUE] = BEGIN
entry.greyed ← TRUE;
RedrawMenu[parent, menu, entry];
IF normalPriority THEN TRUSTED {Process.SetPriority[Process.priorityNormal]};
entry.proc[parent, entry.clientData, button, shift, control ! ABORTED => CONTINUE];
entry.greyed ← FALSE;
RedrawMenu[parent, menu, entry];
WindowManager.RestoreCursor[];
END;
RedrawMenu:
PROC [viewer: Viewer, menu: Menu, entry: MenuEntry] =
BEGIN
IF menu=WindowManagerPrivate.windowMenu
THEN
WindowManagerPrivate.DrawCaptionMenu[viewer, FALSE]
ELSE ViewerOps.PaintViewer[viewer, menu, entry=NIL, entry];
END;
Document:
PUBLIC
PROC [info:
REF
ANY, parent:
REF
ANY, clientData:
REF
ANY ←
NIL,
mouseButton: MouseButton ← red, shift, control: BOOL ← FALSE] = BEGIN
WITH info
SELECT
FROM
doc: REF TEXT => MessageWindow.Append[Rope.FromRefText[doc], TRUE];
doc: REF ClickProc => doc^[parent, clientData, mouseButton, shift, control];
doc: Rope.ROPE => MessageWindow.Append[doc, TRUE];
ENDCASE => ERROR; -- not valid documentation
END;
ClearMenu:
PUBLIC
PROC [menu: Menu, parent: ViewerClasses.Viewer, paint:
BOOL ←
TRUE] =
BEGIN
IF menu#
NIL
AND menu.inverted#
NIL
THEN
BEGIN
IF paint THEN InvertEntry[menu, parent];
menu.inverted ← NIL;
END;
END;
guardedTexture: GraphicsOps.Texture = [
125252B,052525B,125252B,052525B,125252B,052525B,125252B,052525B,
052525B,125252B,052525B,125252B,052525B,125252B,052525B,125252B
];
greyGuard: PUBLIC BOOL ← FALSE; -- grey text of guarded items else strikeout
DrawMenu:
PUBLIC
PROC [menu: Menu, context: Graphics.Context, x, y:
INTEGER,
whatChanged: REF ANY ← NIL] = BEGIN
OPEN Graphics;
line: INTEGER ← 0;
quitEarly: BOOL ← FALSE;
menu.x ← x;
menu.y ← y;
FOR row:
INTEGER
IN [0..menu.linesUsed)
DO
ey: INTEGER ~ y-line;
SetCP[context, x+menuHLeading, y+baselineOffset-line];
FOR e: MenuEntry ← menu.lines[row], e.link
UNTIL e=
NIL
DO
ex: INTEGER ~ x+e.xPos;
IF whatChanged#
NIL
THEN
BEGIN
IF e=whatChanged
THEN
BEGIN
SetColor[context, white];
DrawBox[context, [ex-widthFudge, ey, ex+e.width+widthFudge,
ey+fontHeight-1]];
SetColor[context, black];
quitEarly ← TRUE;
END
ELSE
BEGIN
SetCP[context, e.width+menuHSpace, 0, TRUE];
LOOP;
END;
END;
IF e.greyed
THEN
BEGIN
myGrey: CARDINAL = 001010B;
SetStipple[context, myGrey];
DrawBox[context, [ex-widthFudge, ey, ex+e.width+widthFudge, ey+fontHeight-1]];
SetColor[context, black];
END;
DrawRope[context, e.name];
IF e.guarded
AND e.guardState#armed
THEN
BEGIN
IF greyGuard
THEN
BEGIN
SetCP[context, ex+1, ey+baselineOffset]; -- fake bold with rope drawn one offset
DrawRope[context, e.name];
SetCP[context, -1, 0, TRUE]; -- undo offset
SetColor[context, white];
[] ← SetPaintMode[context, transparent];
GraphicsOps.DrawTexturedBox[context, [ex, ey, ex+e.width+1, ey+fontHeight-1],
guardedTexture];
[] ← SetPaintMode[context, opaque];
SetColor[context, black];
END
ELSE
BEGIN
by: INTEGER ~ ey+baselineOffset+2;
DrawBox[context, [ex-1, by, ex+e.width+1, by+1]];
END;
END;
IF quitEarly THEN RETURN;
SetCP[context, menuHSpace, 0, TRUE];
ENDLOOP;
line ← line + ViewerSpecs.menuHeight;
ENDLOOP;
menu.inverted ← NIL;
END;
ResolveEntry:
PROC [menu: Menu, line, x:
INTEGER]
RETURNS [entry: MenuEntry] =
BEGIN
IF line NOT IN [0..menu.linesUsed) THEN RETURN[NIL];
FOR e: MenuEntry ← menu.lines[line], e.link
UNTIL e=
NIL
OR e.xPos>x
DO
IF x IN [e.xPos..e.xPos+e.width) THEN RETURN[e];
ENDLOOP;
RETURN[NIL];
END;
InvertEntry:
PROC [menu: Menu, parent: Viewer] =
BEGIN
IF ~parent.destroyed
AND ~parent.iconic
THEN
ViewerOps.InvertForMenus[parent, menu.inverted.xPos-widthFudge,
menu.y - (ViewerSpecs.menuHeight*menu.lineInverted),
menu.inverted.width+(2*widthFudge), fontHeight-1];
END;
ComputeFontInfo:
PROC =
BEGIN
min, max: REAL;
[----, min, ----, max] ← Graphics.FontBox[menuGFont];
fontHeight ← Real.RoundC[max-min+1];
baselineOffset ← Real.RoundC[-min];
END;
GreyGuard: UserProfile.ProfileChangedProc =
BEGIN
new: BOOL = UserProfile.Boolean["GreyGuard", FALSE];
IF new#greyGuard
THEN
BEGIN
greyGuard ← new;
ViewerOps.PaintEverything[];
END;
END;
ComputeFontInfo[];
UserProfile.CallWhenProfileChanges[GreyGuard];
END.