MenusImpl.mesa
Copyright Ó 1985, 1986, 1987, 1991 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) June 11, 1985 4:15:15 pm PDT
Doug Wyatt, January 20, 1987 0:09:42 am PST
Bier, February 2, 1989 12:06:47 pm PST
Willie-s, October 8, 1991 1:10 pm PDT
DIRECTORY
CedarProcess USING [SetPriority],
Imager USING [black, Color, Context, MaskRectangleI, SetColor, SetFont, SetXRelI, SetXYI, ShowRope, white],
ImagerFont USING [Extents, FontBoundingBox],
ImagerBackdoor USING [MakeStipple],
Menus USING [armedTime, armingTime, ClickProc, Menu, MenuEntry, MenuEntryRec, MenuRec, MenuLine, MouseButton],
MessageWindow USING [Append],
Process USING [Detach, Milliseconds, MsecToTicks, SetTimeout],
Real USING [Round],
Rope USING [Equal, FromRefText, ROPE],
TIPUser USING [TIPScreenCoords],
VFonts USING [defaultFont, Font, StringWidth],
ViewerClasses USING [Viewer],
ViewerOps USING [PaintViewer],
ViewerPrivate USING [DrawCaptionMenu, InvertForMenus, menuHLeading, menuHSpace, windowMenu],
ViewerSpecs USING [menuHeight],
WindowManager USING [RestoreCursor];
MenusImpl:
CEDAR
MONITOR
LOCKS entry
USING entry: MenuEntry
IMPORTS CedarProcess, Imager, ImagerBackdoor, ImagerFont, MessageWindow, Process, Real, Rope, VFonts, ViewerOps, ViewerPrivate, ViewerSpecs, WindowManager
EXPORTS Menus, ViewerPrivate
SHARES ViewerClasses, ViewerOps
= BEGIN OPEN Menus;
Viewer: TYPE ~ ViewerClasses.Viewer;
menuFont: VFonts.Font ¬ VFonts.defaultFont;
fontHeight: INTEGER ¬ 0;
baselineOffset: INTEGER ¬ 0;
margin: INTEGER = 3;
CreateMenu:
PUBLIC
PROC [lines: MenuLine ¬ 1]
RETURNS [menu: Menu] ~ {
RETURN[NEW[MenuRec ¬ [linesUsed: lines]]]
};
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
newEntry ← CopyEntry[t];
newEntry.greyed ← FALSE;
newEntry.guardState ← IF newEntry.guarded THEN guarded ELSE armed;
IF menu.lines[line]=NIL
THEN menu.lines[line] ← newEntry
ELSE newEntry.link ← newEntry;
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] ¬ CopyEntry[t]
ELSE newEntry ¬ newEntry.link ¬ CopyEntry[t];
newEntry.greyed ¬ FALSE;
newEntry.guardState ¬ IF newEntry.guarded THEN guarded ELSE armed;
ENDLOOP;
};
CopyEntry:
PUBLIC
PROC [oldEntry: MenuEntry]
RETURNS [newEntry: MenuEntry] = {
newEntry ¬ NEW[MenuEntryRec];
newEntry.link ¬ oldEntry.link;
newEntry.name ¬ oldEntry.name;
newEntry.proc ¬ oldEntry.proc;
newEntry.width ¬ oldEntry.width;
newEntry.xPos ¬ oldEntry.xPos;
newEntry.clientData ¬ oldEntry.clientData;
newEntry.documentation ¬ oldEntry.documentation;
newEntry.fork ¬ oldEntry.fork;
newEntry.greyed ¬ oldEntry.greyed;
newEntry.guarded ¬ oldEntry.guarded;
newEntry.guardState ¬ oldEntry.guardState;
};
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] = {
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, menuFont]
]];
SetGuarded:
PUBLIC
PROC [entry: MenuEntry, guard:
BOOL] = {
entry.guarded ¬ guard;
entry.guardState ¬ IF guard THEN guarded ELSE armed;
};
SetClientData:
PUBLIC
PROC [entry: MenuEntry, newData:
REF
ANY]
RETURNS [oldData:
REF
ANY] = {
oldData ¬ entry.clientData;
entry.clientData ¬ newData;
};
SetDocumentation:
PUBLIC
PROC [entry: MenuEntry, newDoc:
REF
ANY]
RETURNS [oldDoc:
REF
ANY] = {
oldDoc ¬ entry.documentation;
entry.documentation ¬ newDoc;
};
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 ¬ ViewerPrivate.menuHLeading;
FOR e: MenuEntry ¬ menu.lines[line].link, e.link
UNTIL e=
NIL
DO
e.xPos ¬ e.xPos + entry.width + ViewerPrivate.menuHSpace;
ENDLOOP;
};
AppendMenuEntry:
PUBLIC
PROC [menu: Menu, entry: MenuEntry, line: MenuLine ¬ 0] = {
entry.link ¬ NIL;
IF menu.lines[line]=
NIL
THEN {entry.xPos ¬ ViewerPrivate.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+ViewerPrivate.menuHSpace;
e.link ¬ entry};
ENDLOOP;
};
MenuFromMenuEntry:
PUBLIC
PROC[entryList: MenuEntry]
RETURNS [Menu] ~ {
menu: Menu ~ CreateMenu[lines: 1];
SetLine[menu: menu, line: 0, entryList: entryList];
RETURN [menu];
};
MenuEntryFind:
PUBLIC
PROC[entryName: Rope.
ROPE, entryList: MenuEntry]
RETURNS [MenuEntry] ~ {
Search for an entry given the name. Will return NIL if target name not found.
menu: Menu ~ MenuFromMenuEntry[entryList: entryList];
RETURN [FindEntry[menu: menu, entryName: entryName]];
};
MenuEntryAppend:
PUBLIC
PROC[entry: MenuEntry, entryList: MenuEntry]
RETURNS [MenuEntry] ~ {
Add the entry to the end of the list.
menu: Menu ~ MenuFromMenuEntry[entryList: entryList];
AppendMenuEntry[menu: menu, entry: entry, line: 0];
RETURN [GetLine[menu: menu, line: 0]];
};
MenuEntryInsert:
PUBLIC
PROC[entry: MenuEntry, entryList: MenuEntry]
RETURNS [MenuEntry] ~ {
Add the entry to the beginning of the list.
menu: Menu ~ MenuFromMenuEntry[entryList: entryList];
InsertMenuEntry[menu: menu, entry: entry, line: 0];
RETURN [GetLine[menu: menu, line: 0]];
};
MenuEntryReplace:
PUBLIC
PROC[entryList: MenuEntry, old: MenuEntry, new: MenuEntry]
RETURNS [MenuEntry] ~ {
Change an existing entry in a menu. If new is NIL then old will be deleted.
Will raise targetNotFound if target entry not found.
menu: Menu ~ MenuFromMenuEntry[entryList: entryList];
ReplaceMenuEntry[menu: menu, oldEntry: old, newEntry: new];
! targetNotFound => RAISE targetNotFound
RETURN [GetLine[menu: menu, line: 0]];
};
targetNotFound: PUBLIC SIGNAL = CODE;
ReplaceMenuEntry:
PUBLIC
PROC [menu: Menu, oldEntry: MenuEntry, newEntry: MenuEntry ¬
NIL] = {
l: MenuLine;
x: INTEGER ¬ ViewerPrivate.menuHLeading;
exit: BOOL ¬ FALSE;
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;
IF menu.lines[l]#NIL THEN menu.lines[l].xPos ¬ ViewerPrivate.menuHLeading;
FOR e: MenuEntry ¬ menu.lines[l], e.link
UNTIL e=
NIL
DO
e.xPos ¬ x;
x ¬ x+e.width+ViewerPrivate.menuHSpace;
ENDLOOP;
};
ChangeNumberOfLines:
PUBLIC
PROC [menu: Menu, newLines: MenuLine] = {
menu.linesUsed ¬ newLines;
};
GetNumberOfLines:
PUBLIC
PROC [menu: Menu]
RETURNS [lines: MenuLine] = {
RETURN[menu.linesUsed];
};
SetLine:
PUBLIC
PROC [menu: Menu, line: MenuLine, entryList: MenuEntry] = {
menu.lines[line] ¬ entryList; menu.linesUsed ¬ MAX[menu.linesUsed, line+1];
};
GetLine:
PUBLIC
PROC [menu: Menu, line: MenuLine]
RETURNS [entryList: MenuEntry] = {
RETURN[menu.lines[line]];
};
MarkMenu:
PUBLIC
PROC [menu: Menu, parent: ViewerClasses.Viewer, mousePos: TIPUser.TIPScreenCoords] = {
delta: INTEGER ~ (menu.y-mousePos.mouseY);
IF delta >= 0
THEN {
row: NAT ~ NAT[delta]/ViewerSpecs.menuHeight;
newEntry: MenuEntry ¬ ResolveEntry[menu, row, mousePos.mouseX];
SELECT
TRUE
FROM
newEntry=
NIL => {
IF menu.inverted#NIL THEN InvertEntry[menu, parent];
menu.inverted ¬ NIL;
};
newEntry#menu.inverted => {
IF menu.inverted#NIL THEN InvertEntry[menu, parent];
menu.inverted ¬ newEntry;
menu.lineInverted ¬ row;
InvertEntry[menu, parent];
};
ENDCASE;
};
};
HitMenu:
PUBLIC
PROC [menu: Menu, parent: ViewerClasses.Viewer, mousePos: TIPUser.TIPScreenCoords, button: MouseButton, shift, control:
BOOL ¬
FALSE] = {
delta: INTEGER ~ (menu.y-mousePos.mouseY);
IF delta >= 0
THEN {
row: NAT ~ NAT[delta]/ViewerSpecs.menuHeight;
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: 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: 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:
BOOL ¬
FALSE, normalPriority:
BOOL ¬
TRUE] = {
entry.greyed ¬ TRUE;
RedrawMenu[parent, menu, entry];
IF normalPriority THEN CedarProcess.SetPriority[normal];
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=ViewerPrivate.windowMenu
THEN ViewerPrivate.DrawCaptionMenu[viewer, FALSE]
ELSE ViewerOps.PaintViewer[viewer, menu, entry=NIL, entry];
};
Document:
PUBLIC
PROC [info:
REF
ANY, parent: Viewer, clientData:
REF
ANY ¬
NIL, mouseButton: MouseButton ¬ red, shift, control:
BOOL ¬
FALSE] = {
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:
BOOL ¬
TRUE] = {
IF menu#
NIL
AND menu.inverted#
NIL
THEN {
IF paint THEN InvertEntry[menu, parent];
menu.inverted ¬ NIL;
};
};
greyGuard: PUBLIC BOOL ¬ FALSE; -- grey text of guarded items else strikeout
myGrey: Imager.Color ~ ImagerBackdoor.MakeStipple[001010B];
DrawMenu:
PUBLIC
PROC [menu: Menu, context: Imager.Context, x, y:
INTEGER,
whatChanged:
REF
ANY ¬
NIL] = {
quitEarly: BOOL ¬ FALSE;
ey: INTEGER ¬ y;
menu.x ¬ x;
menu.y ¬ y;
Imager.SetFont[context, menuFont];
FOR row:
NAT
IN [0..menu.linesUsed)
DO
ey ¬ ey-ViewerSpecs.menuHeight;
Imager.SetXYI[context, x+ViewerPrivate.menuHLeading, ey+baselineOffset];
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[context, Imager.white];
Imager.MaskRectangleI[context, ex-margin, ey, e.width+margin*2, fontHeight];
quitEarly ¬ TRUE;
}
ELSE { Imager.SetXRelI[context, e.width+ViewerPrivate.menuHSpace]; LOOP };
};
IF e.greyed
THEN {
Imager.SetColor[context, myGrey];
Imager.MaskRectangleI[context, ex-margin, ey, e.width+margin*2, fontHeight];
};
Imager.SetColor[context, Imager.black];
Imager.ShowRope[context, e.name];
IF e.guarded
AND e.guardState#armed
THEN {
Imager.MaskRectangleI[context, ex-1, ey+baselineOffset+3, e.width+2, 1];
};
IF quitEarly THEN RETURN;
Imager.SetXRelI[context, ViewerPrivate.menuHSpace];
ENDLOOP;
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 {
menuHeight: INTEGER ~ ViewerSpecs.menuHeight;
x: INTEGER ~ menu.inverted.xPos-margin;
w: INTEGER ~ menu.inverted.width+(2*margin);
y: INTEGER ~ menu.y-menu.lineInverted*menuHeight;
ViewerPrivate.InvertForMenus[parent, x, y, w, -menuHeight];
};
};
ComputeFontInfo:
PROC = {
extents: ImagerFont.Extents ~ ImagerFont.FontBoundingBox[menuFont];
fontHeight ¬ Real.Round[extents.descent+extents.ascent];
baselineOffset ¬ Real.Round[extents.descent];
};
Initialization
ComputeFontInfo[];
END.