MenusImpl.mesa
Copyright © 1982, 1983, 1984 Xerox Corporation. All rights reserved.
Edited by McGregor on October 26, 1982 2:31 pm
Last Edited by: Maxwell, May 24, 1983 7:48 am
Last Edited by: Paul Rovner, June 15, 1983 5:27 pm
Doug Wyatt, September 4, 1984 3:27:22 pm PDT
DIRECTORY
Font USING [Extents, FONT, FontBoundingBox],
Graphics USING [Context],
Imager USING [Color, Context, MaskRectangleI, SetColor, SetXRelI, SetXYI, ShowRope, white],
ImagerOps USING [ColorFromStipple, ImagerFromGraphics],
Menus USING [armedTime, armingTime, ClickProc, CreateMenu, Menu, MenuEntry, MenuEntryRec, MenuLine, MouseButton],
MenusPrivate USING [menuHLeading, menuHSpace],
MessageWindow USING [Append],
Process USING [Detach, Milliseconds, MsecToTicks, priorityNormal, SetPriority, SetTimeout],
Real USING [RoundC],
Rope USING [Equal, FromRefText, ROPE],
TIPUser USING [TIPScreenCoords],
VFonts USING [defaultFont, Font, StringWidth],
ViewerClasses USING [Viewer],
ViewerExtras USING [ImagerFont],
ViewerOps USING [InvertForMenus, PaintViewer],
ViewerSpecs USING [menuHeight],
WindowManager USING [RestoreCursor],
WindowManagerPrivate USING [DrawCaptionMenu, windowMenu];
MenusImpl: CEDAR MONITOR LOCKS entry USING entry: MenuEntry
IMPORTS Font, Imager, ImagerOps, Menus, MessageWindow, Process, Real, Rope, VFonts, ViewerExtras, ViewerOps, WindowManager, WindowManagerPrivate
EXPORTS Menus, MenusPrivate
SHARES Menus, ViewerOps
= BEGIN OPEN Menus, MenusPrivate;
Viewer: TYPE ~ ViewerClasses.Viewer;
menuVFont: VFonts.Font ← VFonts.defaultFont;
menuFont: Font.FONT ← ViewerExtras.ImagerFont[menuVFont];
fontHeight: INTEGER;
baselineOffset: INTEGER;
widthFudge: INTEGER = 3;
CopyMenu: PUBLIC PROC [old: Menu] RETURNS [new: Menu] = {
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;
};
CopyLine: PROC [menu: Menu, line: INTEGER] = {
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]←NEW[MenuEntryRec ← t^]
ELSE newEntry ← newEntry.link ← NEW[MenuEntryRec ← t^];
newEntry.greyed ← FALSE;
newEntry.guardState ← IF newEntry.guarded THEN guarded ELSE armed;
ENDLOOP;
};
CreateEntry: PUBLIC PROC [name: Rope.ROPE, proc: ClickProc, clientData: REF ANYNIL,
documentation: REF ANYNIL, fork: BOOLTRUE, guarded: BOOLFALSE]
RETURNS [entry: MenuEntry] = {
entry ← 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]
]];
};
FindEntry: PUBLIC PROC [menu: Menu, entryName: Rope.ROPE] RETURNS [entry: MenuEntry] =
{
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;
};
InsertMenuEntry: PUBLIC PROC [menu: Menu, entry: MenuEntry, line: MenuLine ← 0] = {
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;
};
AppendMenuEntry: PUBLIC PROC [menu: Menu, entry: MenuEntry, line: MenuLine ← 0] = {
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;
};
targetNotFound: PUBLIC SIGNAL = CODE;
ReplaceMenuEntry: PUBLIC PROC [menu: Menu, oldEntry: MenuEntry,
newEntry: MenuEntry ← NIL] = {
l: MenuLine;
x: INTEGER ← menuHLeading;
exit: BOOLFALSE;
FOR l IN MenuLine UNTIL menu.lines[l]=NIL DO
IF oldEntry=menu.lines[l] THEN {
IF newEntry=NIL THEN menu.lines[l] ← menu.lines[l].link
ELSE {
newEntry.link ← oldEntry.link;
menu.lines[l] ← newEntry;
};
EXIT;
}
ELSE FOR e: MenuEntry ← menu.lines[l], e.link UNTIL e.link=NIL DO
IF e.link=oldEntry THEN {
IF newEntry=NIL THEN e.link ← e.link.link
ELSE {
newEntry.link ← oldEntry.link;
e.link ← newEntry;
};
exit ← TRUE; EXIT;
};
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;
};
MenuRow: PROC [menu: Menu, viewer: ViewerClasses.Viewer, y: INTEGER]
RETURNS [INTEGER] = INLINE {
RETURN[IF menu.linesUsed=1 THEN 0 ELSE
(menu.y+ViewerSpecs.menuHeight-y-viewer.wy)/ViewerSpecs.menuHeight];
};
MarkMenu: PUBLIC PROC [menu: Menus.Menu, parent: ViewerClasses.Viewer,
mousePos: TIPUser.TIPScreenCoords] = {
row: INTEGER ← MenuRow[menu, parent, mousePos.mouseY];
newEntry: MenuEntry ← ResolveEntry[menu, row, mousePos.mouseX];
IF newEntry=NIL THEN {
IF menu.inverted#NIL THEN InvertEntry[menu, parent];
menu.inverted ← NIL;
}
ELSE IF newEntry#menu.inverted THEN {
IF menu.inverted#NIL THEN InvertEntry[menu, parent];
menu.inverted ← newEntry;
menu.lineInverted ← row;
InvertEntry[menu, parent];
};
};
HitMenu: PUBLIC PROC [menu: Menus.Menu, parent: ViewerClasses.Viewer,
mousePos: TIPUser.TIPScreenCoords, button: MouseButton, shift, control: BOOLFALSE] = {
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];
};
ProcessMenuHit: ENTRY PROC [entry: MenuEntry, menu: Menus.Menu,
parent: ViewerClasses.Viewer, button: MouseButton, shift, control: BOOL] = TRUSTED {
SELECT entry.guardState FROM
guarded => {
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]];
};
arming=> NULL; -- no action
armed => {
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];
};
ENDCASE;
};
ArmMenuProc: ENTRY PROC [entry: MenuEntry, menu: Menus.Menu,
parent: ViewerClasses.Viewer] = {
menuWaitCondition: CONDITION;
assert: state=arming
TRUSTED {Process.SetTimeout[@menuWaitCondition, Process.MsecToTicks[armingTime]]};
WAIT menuWaitCondition;
IF entry.guardState = arming THEN {
entry.guardState ← armed;
RedrawMenu[parent, menu, entry];
TRUSTED {Process.SetTimeout[@menuWaitCondition, Process.MsecToTicks[armedTime]]};
WAIT menuWaitCondition;
};
IF entry.guardState#guarded THEN {
entry.guardState ← guarded;
RedrawMenu[parent, menu, entry];
};
};
MenuPusher: PROC [entry: MenuEntry, menu: Menu, parent: ViewerClasses.Viewer,
button: MouseButton, shift, control: BOOLFALSE, normalPriority: BOOLTRUE] = {
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[];
};
RedrawMenu: PROC [viewer: Viewer, menu: Menu, entry: MenuEntry] = {
IF menu=WindowManagerPrivate.windowMenu THEN
WindowManagerPrivate.DrawCaptionMenu[viewer, FALSE]
ELSE ViewerOps.PaintViewer[viewer, menu, entry=NIL, entry];
};
Document: PUBLIC PROC [info: REF ANY, parent: REF ANY, clientData: REF ANYNIL,
mouseButton: MouseButton ← red, shift, control: BOOLFALSE] = {
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
};
ClearMenu: PUBLIC PROC [menu: Menu, parent: ViewerClasses.Viewer, paint: BOOLTRUE] =
{
IF menu#NIL AND menu.inverted#NIL THEN {
IF paint THEN InvertEntry[menu, parent];
menu.inverted ← NIL;
};
};
greyGuard: PUBLIC BOOLFALSE; -- grey text of guarded items else strikeout
myGrey: Imager.Color ~ ImagerOps.ColorFromStipple[001010B];
DrawMenu: PUBLIC PROC [menu: Menu, context: Graphics.Context, x, y: INTEGER,
whatChanged: REF ANYNIL] = {
imager: Imager.Context ~ ImagerOps.ImagerFromGraphics[context];
line: INTEGER ← 0;
quitEarly: BOOLFALSE;
menu.x ← x;
menu.y ← y;
FOR row: INTEGER IN [0..menu.linesUsed) DO
ey: INTEGER ~ y-line;
Imager.SetXYI[imager, 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 {
IF e=whatChanged THEN {
Imager.SetColor[imager, Imager.white];
Imager.MaskRectangleI[context: imager,
x: ex-widthFudge, y: ey, w: e.width+2*widthFudge, h: fontHeight-1];
quitEarly ← TRUE;
}
ELSE { Imager.SetXRelI[imager, e.width+menuHSpace]; LOOP };
};
IF e.greyed THEN {
Imager.SetColor[imager, myGrey];
Imager.MaskRectangleI[context: imager,
x: ex-widthFudge, y: ey, w: e.width+2*widthFudge, h: fontHeight-1];
};
Imager.ShowRope[imager, e.name];
IF e.guarded AND e.guardState#armed THEN {
by: INTEGER ~ ey+baselineOffset+2;
Imager.MaskRectangleI[context: imager, x: ex-1, y: by, w: e.width+2, h: 1];
};
IF quitEarly THEN RETURN;
Imager.SetXRelI[imager, menuHSpace];
ENDLOOP;
line ← line + ViewerSpecs.menuHeight;
ENDLOOP;
menu.inverted ← NIL;
};
ResolveEntry: PROC [menu: Menu, line, x: INTEGER] RETURNS [entry: MenuEntry] = {
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];
};
InvertEntry: PROC [menu: Menu, parent: Viewer] = {
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];
};
ComputeFontInfo: PROC = {
min, max: REAL;
extents: Font.Extents ~ Font.FontBoundingBox[menuFont];
min ← -extents.descent;
max ← extents.ascent;
fontHeight ← Real.RoundC[max-min+1];
baselineOffset ← Real.RoundC[-min];
};
ComputeFontInfo[];
END.