MenusImpl.mesa; Written by S. McGregor
Edited by McGregor on August 4, 1983 11:22 am
Last Edited by: Maxwell, May 24, 1983 7:48 am
Last Edited by: Pausch, August 25, 1983 10:29 am
DIRECTORY
Imager,
Interminal,
IO,
Menus,
MenusPrivate,
MessageWindow USING [Append, Clear],
Process USING [Detach, Milliseconds, MsecToTicks, priorityNormal, SetPriority, SetTimeout],
Real USING [RoundC],
Rope USING [Equal, FromRefText, ROPE],
UserTerminal,
TIPUser USING [TIPScreenCoords],
UserProfile USING [Boolean, CallWhenProfileChanges, Number, ProfileChangedProc],
VFonts USING [defaultFont, Font, FontAscent, FontHeight, RopeWidth],
ViewerBLT USING [ChangeMenuHeight],
ViewerClasses,
ViewerOps USING [EnumProc, EnumerateViewers, InvertForMenus, PaintViewer],
ViewerSpecs USING [captionHeight, menuHeight, windowBorderSize],
WindowManager USING [RestoreCursor],
WindowManagerPrivate USING [closePos, DrawCaptionMenu, growPos, windowMenus];
MenusImpl: CEDAR MONITOR LOCKS entry USING entry: EntryState
IMPORTS IO, Imager, MessageWindow, Process, Real, Rope, UserProfile, UserTerminal, VFonts, ViewerBLT, ViewerOps, WindowManager, WindowManagerPrivate
EXPORTS Menus, MenusPrivate
SHARES ViewerOps =
BEGIN OPEN Menus, MenusPrivate;
Viewer: TYPE = ViewerClasses.Viewer;
menuFont: VFonts.Font ← VFonts.defaultFont;
fontHeight: INTEGER;
baselineOffset: INTEGER;
menuHLeading: INTEGER = 5; -- white space before first entry
menuHSpace: INTEGER = 12; -- white space between entries
registeredMenus: LIST OF InternalFormatMenuRef ← NIL;
widthFudge: INTEGER = 3;
targetNotFound: PUBLIC SIGNAL = CODE;
MarkMenu:
PUBLIC
PROC [menus: ViewerMenus, parent: Viewer, mousePos: TIPUser.TIPScreenCoords] = {
entry: EntryState ← ResolveEntry[menus, mousePos];
IF menus.inverted # entry
THEN {
IF menus.inverted # NIL THEN InvertEntry[menus,parent];
menus.inverted ← entry;
IF menus.inverted # NIL THEN InvertEntry[menus,parent];
};
};
HitMenu:
PUBLIC
PROC [menus: ViewerMenus, parent: Viewer, mousePos: TIPUser.TIPScreenCoords, trigger: MenuEntryTrigger] =
BEGIN
entry: EntryState ← ResolveEntry[menus, mousePos];
hit: BOOL ← menus.inverted#NIL AND entry=menus.inverted;
IF menus.inverted#NIL THEN InvertEntry[menus, parent]; -- take down
menus.inverted ← NIL;
IF hit THEN ProcessMenuHit[entry, menus, parent, trigger];
END;
ProcessMenuHit:
ENTRY
PROC [entry: EntryState, menus: ViewerMenus, parent: Viewer, trigger: MenuEntryTrigger] =
TRUSTED
BEGIN
response: REF ANY;
SELECT entry.guardState
FROM
guarded =>
BEGIN
entry.guardState ← arming;
Process.Detach[FORK ArmMenuProc[entry, menus, parent]];
response ← FindNotifyRecord[entry,trigger].guardResponse;
IF response#
NIL
THEN Process.Detach[
FORK GuardResponse[response] ];
END;
arming=> NULL; -- no action
armed =>
BEGIN
IF entry.commonData.guarded THEN entry.guardState ← guarded;
MenuPusher[entry, menus, parent, trigger, FALSE];
END;
ENDCASE;
END;
ArmMenuProc:
ENTRY
PROC [entry: EntryState, menus: ViewerMenus, parent: 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, menus, entry];
TRUSTED {Process.SetTimeout[@menuWaitCondition, Process.MsecToTicks[armedTime]]};
WAIT menuWaitCondition;
END;
IF entry.guardState#guarded
THEN
BEGIN
entry.guardState ← guarded;
RedrawMenu[parent, menus, entry];
END;
END;
MenuPusher:
PROC [entry: EntryState, menus: ViewerMenus, parent: Viewer, trigger: MenuEntryTrigger, normalPriority:
BOOL ←
TRUE] =
BEGIN
changeOccurred: BOOLEAN ← FALSE;
notifyRecord: EntryNotifyRecord ← FindNotifyRecord[entry,trigger];
notifyProc: ViewerClasses.NotifyProc ← FindViewerMenu[menus,entry].commonData.notify;
IF notifyProc =
NIL
THEN {
notifyProc ← parent.class.notify;
IF notifyProc = NIL THEN Debug[msg: "using NIL class notifyproc", waitForLeftShift: TRUE, level: 2]
ELSE Debug[msg: "using NON-NIL class notifyproc", waitForLeftShift: TRUE, level: 2]
}
ELSE Debug[msg: "using specially specified notifyproc", waitForLeftShift: TRUE, level: 2];
entry.greyed ← TRUE;
RedrawMenu[parent, menus, entry];
IF normalPriority THEN TRUSTED {Process.SetPriority[Process.priorityNormal]};
this is where we actually tell the client about the menu-invoked function:
IF notifyProc # NIL THEN notifyProc[parent, notifyRecord.notifyData];
entry.greyed ← FALSE;
RedrawMenu[parent, menus, entry];
changeOccurred ← FALSE;
FOR m:
LIST
OF Rope.
ROPE ← notifyRecord.makeActive, m.rest
UNTIL m =
NIL
DO
MakeActive[viewer: parent, menu: m.first, paint: FALSE];
changeOccurred ← TRUE;
ENDLOOP;
FOR m:
LIST
OF Rope.
ROPE ← notifyRecord.makeInActive, m.rest
UNTIL m =
NIL
DO
MakeInActive[viewer: parent, menu: m.first, paint: FALSE];
changeOccurred ← TRUE;
ENDLOOP;
FOR m:
LIST
OF Rope.
ROPE ← notifyRecord.toggle, m.rest
UNTIL m =
NIL
DO
Toggle[viewer: parent, menu: m.first, paint: FALSE];
changeOccurred ← TRUE;
ENDLOOP;
IF changeOccurred THEN RePaintBecauseOfMenuChange[parent];
WindowManager.RestoreCursor[];
END;
RedrawMenu:
PROC [viewer: Viewer, menus: ViewerMenus, entry: EntryState] =
BEGIN
IF menus = WindowManagerPrivate.windowMenus
THEN
WindowManagerPrivate.DrawCaptionMenu[viewer, FALSE]
ELSE ViewerOps.PaintViewer[viewer: viewer, hint: menu, whatChanged: entry];
END;
GuardResponse:
PROC [response:
REF
ANY] =
BEGIN
WITH response
SELECT
FROM
response: Rope.ROPE => MessageWindow.Append[response, TRUE];
response: REF UnGuardRec => response.proc[response.data];
ENDCASE => ERROR; -- not valid response
END;
ClearMenu:
PUBLIC
PROC [menus: ViewerMenus, parent: Viewer, paint:
BOOL ←
TRUE] =
BEGIN
IF menus.inverted#
NIL
THEN
BEGIN
IF paint THEN InvertEntry[menus, parent];
menus.inverted ← NIL;
END;
END;
DrawSingleEntry:
PROC [viewer: Viewer, context: Imager.Context, entry: EntryState, clearFirst:
BOOLEAN ←
TRUE] = {
OPEN Imager;
myGrey: REF CARDINAL = NEW[CARDINAL ← 001010B];
lowerLeftX: INTEGER ← viewer.wx + entry.xPos;
lowerLeftY: INTEGER ← viewer.wy + entry.yPos;
IF clearFirst
THEN {
SetColor[context, white];
MaskIntRectangle[context, [lowerLeftX - widthFudge, lowerLeftY, entry.width + (2*widthFudge), fontHeight-1]];
SetColor[context, black];
};
IF entry.greyed
THEN {
SetColor[context, myGrey];
MaskIntRectangle[context, [lowerLeftX - widthFudge, lowerLeftY, entry.width + (2*widthFudge), fontHeight-1]];
SetColor[context, black];
};
WITH entry.commonData.displayData
SELECT
FROM
r: Rope.
ROPE => {
SetIntCP[context, [lowerLeftX, lowerLeftY + baselineOffset]];
MaskCharacters[context, VFonts.defaultFont, r];
};
user:
REF DrawingRec => {
CallUserDrawProc:
PROC = {
separate procedure because it sets a clipping rectangle
ClipIntRectangle[context, [lowerLeftX-widthFudge, lowerLeftY, entry.width+(2*widthFudge), fontHeight-1]];
[] ← user.proc[op: draw, ctx: context, clientData: user.data];
};
SetIntCP[context, [lowerLeftX, lowerLeftY]];
DoSave[context, CallUserDrawProc];
};
ENDCASE => ERROR;
draw line through guarded entries (constants of '2' and '1' look good, aren't used elsewhere)
IF entry.commonData.guarded AND entry.guardState # armed THEN MaskIntRectangle[context, [lowerLeftX - 1, lowerLeftY + baselineOffset+2, entry.width + 2, 1]];
};
DrawMenu:
PUBLIC
PROC [v: Viewer, menus: ViewerMenus, context: Imager.Context, whatChanged:
REF
ANY ←
NIL] =
BEGIN
NB: the menus parm need not be the menus for the viewer; it could also be the windowManger caption menu
Debug[msg: IO.PutFR["drawing menus of height: %d", IO.int[menus.h]], level: 2];
IF whatChanged # NIL THEN DrawSingleEntry[v, context, NARROW[whatChanged, EntryState]]
ELSE {
FOR m:
LIST
OF ViewerMenu ← menus.list, m.rest
UNTIL m =
NIL
DO
IF m.first.active
THEN
FOR e:
LIST
OF EntryState ← m.first.entries, e.rest
UNTIL e =
NIL
DO
DrawSingleEntry[v, context, e.first, FALSE];
ENDLOOP;
ENDLOOP;
menus.inverted ← NIL;
};
END;
resolveCacheMenus: ViewerMenus ← NIL;
resolveCacheMenu: ViewerMenu ← NIL;
rce: EntryState ← NIL; -- stands for ResolveCacheEntry
ResolveEntry:
PROC [menus: ViewerMenus, mousePos: TIPUser.TIPScreenCoords]
RETURNS [entry: EntryState] = {
this has to go FAST, so we cache the last entry. In order for this cache to be legit, we have to save a pointer to the entry, which is the cached entry, the menu (TYPE ViewerMenu) that contains it, and the ViewerMenus that contained the menu. If we just tested against the entry, we could get fooled by an entry in a menu that had been made inactive.
Cache test:
IF resolveCacheMenus = menus
THEN
IF resolveCacheMenu.active
THEN {
IF mousePos.mouseX IN [rce.xPos..(rce.xPos + rce.width)] AND mousePos.mouseY IN [rce.yPos..(rce.yPos+(fontHeight-1))] THEN RETURN[rce];
};
if cache fails, we test everybody.
FOR m:
LIST
OF ViewerMenu ← menus.list, m.rest
UNTIL m =
NIL
DO
IF NOT m.first.active THEN LOOP;
FOR e:
LIST
OF EntryState ← m.first.entries, e.rest
UNTIL e =
NIL
DO
IF mousePos.mouseX
IN [e.first.xPos..(e.first.xPos + e.first.width)]
AND mousePos.mouseY
IN [e.first.yPos..(e.first.yPos+(fontHeight-1))]
THEN {
now we must reset the cache:
resolveCacheMenus ← menus;
resolveCacheMenu ← m.first;
rce ← e.first;
RETURN[e.first];
};
ENDLOOP;
ENDLOOP;
RETURN[NIL];
};
InvertEntry:
PROC [menus: ViewerMenus, parent: Viewer] = {
IF ~parent.destroyed
AND ~parent.iconic
THEN {
entry: EntryState ← menus.inverted;
ViewerOps.InvertForMenus[parent, (parent.wx+entry.xPos)-widthFudge, parent.wy+entry.yPos, entry.width+(2*widthFudge), fontHeight-1];
};
};
ComputeFontInfo:
PROC =
BEGIN
ascent: INTEGER = VFonts.FontAscent[menuFont];
fontHeight ← VFonts.FontHeight[menuFont];
baselineOffset ← Real.RoundC[fontHeight-ascent];
END;
FindNotifyRecord:
PROC [entry: EntryState, trigger: MenuEntryTrigger]
RETURNS [answer: EntryNotifyRecord] = {
FOR l:
LIST
OF EntryNotifyRecord ← entry.commonData.actions, l.rest
UNTIL l =
NIL
DO
FOR t:
LIST
OF MenuEntryTrigger ← l.first.trigger, t.rest
UNTIL t =
NIL
DO
NOTE! the following line produces a compiler warning that can be safely ignored.
IF t.first = trigger OR t.first = all THEN RETURN[l.first];
ENDLOOP;
ENDLOOP;
ERROR;
};
FindViewerMenu:
PROC [menus: ViewerMenus, entry: EntryState]
RETURNS [menu: ViewerMenu] = {
FOR l:
LIST
OF ViewerMenu ← menus.list, l.rest
UNTIL l =
NIL
DO
FOR e:
LIST
OF EntryState ← l.first.entries, e.rest
UNTIL e =
NIL
DO
IF e.first = entry THEN RETURN[l.first];
ENDLOOP;
ENDLOOP;
RETURN[NIL];
};
CreateInternalFormatMenu:
PROC [menu: Menu]
RETURNS [answer: InternalFormatMenuRef] = {
note that CONS'ing builds the list in the wrong order, so we do it twice! (gross ...)
reversedList: LIST OF EntryRef ← NIL;
answer ← NEW[InternalFormatMenu];
answer.name ← menu.name;
answer.beginsActive ← menu.beginsActive;
answer.breakBefore ← menu.breakBefore;
answer.breakAfter ← menu.breakAfter;
answer.notify ← menu.notify;
answer.entries ← NIL;
FOR e:
LIST
OF Entry ← menu.entries, e.rest
UNTIL e =
NIL
DO
reversedList ← CONS[MakeNewEntry[e.first], reversedList];
ENDLOOP;
FOR e:
LIST
OF EntryRef ← reversedList, e.rest
UNTIL e =
NIL
DO
answer.entries ← CONS[e.first, answer.entries];
ENDLOOP;
RETURN[answer];
};
MakeNewEntry:
PROC[entry: Entry]
RETURNS[answer: EntryRef] = {
answer ← NEW[Entry ← entry];
the name should be used for displaydata if nothing special is specified.
IF answer.displayData = NIL THEN answer.displayData ← answer.name
ELSE WITH answer.displayData SELECT FROM
this is here so that user-created entries that have displaydata fields of TYPE REF text can be converted to Rope.ROPE. If the compiler ever defaults quoted text strings, this can be safely removed.
dispData: REF TEXT => answer.displayData ← Rope.FromRefText[dispData];
okRope: Rope.ROPE => NULL;
okProc: REF DrawingRec => NULL;
ENDCASE => ERROR; -- invalid data type for DisplayData field.
this is here so that user-created entries that have displaydata fields of TYPE REF text can be converted to Rope.ROPE. If the compiler ever defaults quoted text strings, this can be safely removed.
FOR t:
LIST
OF EntryNotifyRecord ← answer.actions, t.rest
UNTIL t =
NIL
DO
IF t.first.guardResponse #
NIL
THEN
WITH t.first.guardResponse
SELECT
FROM
textRef: REF TEXT => t.first.guardResponse ← Rope.FromRefText[textRef];
okRope: Rope.ROPE => NULL;
okRec: REF UnGuardRec => NULL;
ENDCASE => ERROR; -- invalid data type for guardResponse field.
ENDLOOP;
RETURN[answer];
CreateExternalFormatMenu:
PROC [menu: InternalFormatMenuRef]
RETURNS [Menu] = {
note that CONS'ing builds the list in the wrong order, so we do it twice! (gross ...)
reversedList: LIST OF Entry ← NIL;
answer: REF Menu ← NEW[Menu];
answer.name ← menu.name;
answer.beginsActive ← menu.beginsActive;
answer.breakBefore ← menu.breakBefore;
answer.breakAfter ← menu.breakAfter;
answer.notify ← menu.notify;
answer.entries ← NIL;
FOR e:
LIST
OF EntryRef ← menu.entries, e.rest
UNTIL e =
NIL
DO
temp: EntryRef ← NEW[Entry ← e.first^];
reversedList ← CONS[temp^, reversedList];
ENDLOOP;
FOR e:
LIST
OF Entry ← reversedList, e.rest
UNTIL e =
NIL
DO
answer.entries ← CONS[e.first, answer.entries];
ENDLOOP;
RETURN[answer^];
};
RegisterMenu:
PUBLIC
PROC [menu: Menu] = {
registeredMenus ← CONS[CreateInternalFormatMenu[menu], registeredMenus];
};
AlreadyRegistered:
PUBLIC
PROC [name: Rope.
ROPE]
RETURNS [registered:
BOOLEAN] = {
FOR m:
LIST
OF InternalFormatMenuRef ← registeredMenus, m.rest
UNTIL m =
NIL
DO
IF Rope.Equal[m.first.name, name] THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
};
ReRegisterMenu:
PUBLIC
PROC [menu: Menu, paint:
BOOL ←
TRUE] = {
newMenu: ViewerMenuRec;
MenuDefinitonChanged: ViewerOps.EnumProc = {
[v: viewer] RETURNS [BOOL ← TRUE]
FOR m:
LIST
OF ViewerMenu ←
NARROW[v.menus, ViewerMenus].list, m.rest
UNTIL m =
NIL
DO
IF Rope.Equal[menu.name, m.first.commonData.name] THEN m.first ← NEW[ViewerMenuRec ← newMenu];
ENDLOOP;
IF paint THEN RePaintBecauseOfMenuChange[v];
};
FOR m:
LIST
OF InternalFormatMenuRef ← registeredMenus, m.rest
UNTIL m =
NIL
DO
IF Rope.Equal[m.first.name, menu.name]
THEN {
m.first ← CreateInternalFormatMenu[menu];
newMenu ← MakeNewViewerMenuRec[m.first];
ViewerOps.EnumerateViewers[MenuDefinitonChanged];
RETURN;
};
ENDLOOP;
SIGNAL targetNotFound;
};
GetRegisteredMenu:
PUBLIC
PROC [name: Rope.
ROPE]
RETURNS [menu: Menu] = {
FOR m:
LIST
OF InternalFormatMenuRef ← registeredMenus, m.rest
UNTIL m =
NIL
DO
IF Rope.Equal[m.first.name, menu.name] THEN RETURN[CreateExternalFormatMenu[m.first] ];
ENDLOOP;
SIGNAL targetNotFound;
};
ClearMenus:
PUBLIC
PROC [viewer: Viewer, paint:
BOOL ←
TRUE] = {
viewer.menus ← NIL;
IF paint THEN RePaintBecauseOfMenuChange[viewer];
};
AddMenu:
PUBLIC
PROC [viewer: Viewer, name: Rope.
ROPE, paint:
BOOL ←
TRUE, addBefore: Rope.
ROPE ←
NIL] = {
IF viewer.menus =
NIL
THEN {
IF addBefore # NIL THEN SIGNAL targetNotFound;
viewer.menus ← NEW[ViewerMenusRec];
NARROW[viewer.menus, ViewerMenus].list ← LIST[MakeNewViewerMenu[name]];
}
ELSE
IF addBefore #
NIL
THEN {
place in the slot before 'addBefore'
prev: LIST OF ViewerMenu ← NIL;
foundIt: BOOLEAN ← FALSE;
FOR m:
LIST
OF ViewerMenu ←
NARROW[viewer.menus, ViewerMenus].list, m.rest
UNTIL m =
NIL
DO
IF Rope.Equal[m.first.commonData.name, name]
THEN {
prev.rest ← CONS[MakeNewViewerMenu[name], m];
foundIt ← TRUE;
};
prev ← m;
ENDLOOP;
IF NOT foundIt THEN SIGNAL targetNotFound;
}
ELSE
IF GetInternalFormatMenuRef[name].breakBefore
THEN {
place at the end of the list if it wants its own separate line.
prev: LIST OF ViewerMenu ← NIL;
FOR m:
LIST
OF ViewerMenu ←
NARROW[viewer.menus, ViewerMenus].list, m.rest
UNTIL m =
NIL
DO
prev ← m;
ENDLOOP;
prev.rest ← LIST[MakeNewViewerMenu[name]];
}
ELSE {
place it at the first place without a 'breakAfter' that has a 'breakBefore' following it
prev: LIST OF ViewerMenu ← NIL;
FOR m:
LIST
OF ViewerMenu ←
NARROW[viewer.menus, ViewerMenus].list, m.rest
UNTIL m =
NIL
OR ((prev #
NIL
AND prev.first.commonData.breakAfter =
FALSE)
AND m.first.commonData.breakBefore)
DO
prev ← m;
ENDLOOP;
prev.rest ← CONS[MakeNewViewerMenu[name],prev.rest];
};
IF paint THEN RePaintBecauseOfMenuChange[viewer];
};
ViewerlessAddMenu:
PUBLIC
PROC [name: Rope.
ROPE, addBefore: Rope.
ROPE ←
NIL, paint:
BOOL ←
TRUE] = {
provided for convenience of BuildWindowMenus in WindowManagerImpl
IF WindowManagerPrivate.windowMenus =
NIL
THEN {
IF addBefore # NIL THEN SIGNAL targetNotFound;
WindowManagerPrivate.windowMenus ← NEW[ViewerMenusRec];
WindowManagerPrivate.windowMenus.list ← LIST[MakeNewViewerMenu[name]];
}
ELSE
IF addBefore #
NIL
THEN {
place in the slot before 'addBefore'
prev: LIST OF ViewerMenu ← NIL;
foundIt: BOOLEAN ← FALSE;
FOR m:
LIST
OF ViewerMenu ← WindowManagerPrivate.windowMenus.list, m.rest
UNTIL m =
NIL
DO
IF Rope.Equal[m.first.commonData.name, name]
THEN {
prev.rest ← CONS[MakeNewViewerMenu[name], m];
foundIt ← TRUE;
};
prev ← m;
ENDLOOP;
IF NOT foundIt THEN SIGNAL targetNotFound;
}
ELSE
IF GetInternalFormatMenuRef[name].breakBefore
THEN {
place at the end of the list if it wants its own separate line.
prev: LIST OF ViewerMenu ← NIL;
FOR m:
LIST
OF ViewerMenu ← WindowManagerPrivate.windowMenus.list, m.rest
UNTIL m =
NIL
DO
prev ← m;
ENDLOOP;
prev.rest ← LIST[MakeNewViewerMenu[name]];
}
ELSE {
place it at the first place without a 'breakAfter' that has a 'breakBefore' following it
prev: LIST OF ViewerMenu ← NIL;
FOR m:
LIST
OF ViewerMenu ← WindowManagerPrivate.windowMenus.list, m.rest
UNTIL m =
NIL
OR ((prev #
NIL
AND prev.first.commonData.breakAfter =
FALSE)
AND m.first.commonData.breakBefore)
DO
prev ← m;
ENDLOOP;
prev.rest ← CONS[MakeNewViewerMenu[name],prev.rest];
};
};
MakeNewViewerMenu:
PROC [name: Rope.
ROPE]
RETURNS [answer: ViewerMenu] = {
FOR m:
LIST
OF InternalFormatMenuRef ← registeredMenus, m.rest
UNTIL m =
NIL
DO
IF Rope.Equal[m.first.name, name] THEN RETURN[NEW[ViewerMenuRec ← MakeNewViewerMenuRec[m.first] ] ];
ENDLOOP;
SIGNAL targetNotFound;
};
MakeNewViewerMenuRec:
PROC [data: InternalFormatMenuRef]
RETURNS [answer: ViewerMenuRec] = {
answer.commonData ← data;
answer.active ← answer.commonData.beginsActive;
answer.entries ← MakeNewViewerMenuEntries[data];
RETURN[answer];
};
MakeNewViewerMenuEntries:
PROC [menu: InternalFormatMenuRef]
RETURNS [answer:
LIST
OF EntryState] = {
reversedList: LIST OF EntryState ← NIL;
answer ← NIL;
FOR e:
LIST
OF EntryRef ← menu.entries, e.rest
UNTIL e =
NIL
DO
reversedList ← CONS[NEW[EntryStateRec ← [commonData: e.first]],reversedList];
ENDLOOP;
FOR e:
LIST
OF EntryState ← reversedList, e.rest
UNTIL e =
NIL
DO
IF e.first.commonData.guarded THEN e.first.guardState ← guarded;
answer ← CONS[e.first, answer];
ENDLOOP;
RETURN[answer];
};
MakeActive:
PUBLIC
PROC [viewer: Viewer, menu: Rope.
ROPE, paint:
BOOL ←
TRUE] = {
FOR m:
LIST
OF ViewerMenu ←
NARROW[viewer.menus, ViewerMenus].list, m.rest
UNTIL m =
NIL
DO
IF Rope.Equal[m.first.commonData.name, menu]
THEN {
IF
NOT m.first.active
THEN {
m.first.active ← TRUE;
IF paint THEN RePaintBecauseOfMenuChange[viewer];
};
RETURN;
};
ENDLOOP;
SIGNAL targetNotFound;
};
MakeInActive:
PUBLIC
PROC [viewer: Viewer, menu: Rope.
ROPE, paint:
BOOL ←
TRUE] = {
FOR m:
LIST
OF ViewerMenu ←
NARROW[viewer.menus, ViewerMenus].list, m.rest
UNTIL m =
NIL
DO
IF Rope.Equal[m.first.commonData.name, menu]
THEN {
IF m.first.active
THEN {
m.first.active ← FALSE;
IF paint THEN RePaintBecauseOfMenuChange[viewer];
};
RETURN;
};
ENDLOOP;
SIGNAL targetNotFound;
};
Toggle:
PUBLIC
PROC [viewer: Viewer, menu: Rope.
ROPE, paint:
BOOL ←
TRUE] = {
FOR m:
LIST
OF ViewerMenu ←
NARROW[viewer.menus, ViewerMenus].list, m.rest
UNTIL m =
NIL
DO
IF Rope.Equal[m.first.commonData.name, menu]
THEN {
m.first.active ← NOT m.first.active;
IF paint THEN RePaintBecauseOfMenuChange[viewer];
RETURN;
};
ENDLOOP;
SIGNAL targetNotFound;
};
MenuInViewer:
PUBLIC
PROC [viewer: Viewer, menu: Rope.
ROPE]
RETURNS [exists:
BOOLEAN] = {
FOR m:
LIST
OF ViewerMenu ←
NARROW[viewer.menus, ViewerMenus].list, m.rest
UNTIL m =
NIL
DO
IF Rope.Equal[m.first.commonData.name, menu] THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
};
The user-profilible menu options:
wrap: BOOLEAN ← TRUE; -- TRUE iff user wants menu entries to ALWAYS be displayed
wrapIndent: INTEGER ← (menuHLeading*4); -- indention on non-first lines of wrapped menu
tooNarrowToWrapMenus: INTEGER ← 150; -- don't wrap if the window is ridiculously narrow
menu wrapping is User Profilible: this routine is called everytime the .profile gets edited
ReEstablishUserProfileParamaters:
PUBLIC UserProfile.ProfileChangedProc = {
wrap ← UserProfile.Boolean[key: "ViewerMenusWrap", default: TRUE];
wrapIndent ← UserProfile.Number[key: "ViewerMenusWrapIndent", default: (menuHLeading*4)];
tooNarrowToWrapMenus ← UserProfile.Number["ViewerMenusTooNarrowToWrap", 150];
};
ReComputeMenus:
PUBLIC
PROC [v: Viewer]
RETURNS[heightDelta:
INTEGER] = {
NOTE: the returned value is the change in the menu's height as a result of recomputation. This is important so that the client space may be adjusted appropriately.
NOTE: menus coordinates are computed and stored RELATIVE to the viewer
originalHeight: INTEGER;
prevLineHadBreakafter: BOOLEAN ← TRUE; -- starting true forces first line to move down
wbs: INTEGER ← IF v.border THEN ViewerSpecs.windowBorderSize ELSE 0;
TopLeftCornerX: INTEGER ← wbs;
TopLeftCornerY: INTEGER ← v.wh-ViewerSpecs.captionHeight;
CurrentX: INTEGER ← TopLeftCornerX + menuHLeading;
CurrentY: INTEGER ← TopLeftCornerY;
IF v.menus=NIL OR v.iconic THEN RETURN[0];
originalHeight ← NARROW[v.menus, ViewerMenus].h;
IF wrap THEN Debug[msg: IO.PutFR["ReComputeMenus for %g ==> wrap is true", IO.rope[ NARROW[v.menus, ViewerMenus].list.first.commonData.name]], level: 2]
ELSE Debug[msg: IO.PutFR["ReComputeMenus for %g ==> wrap is false", IO.rope[NARROW[v.menus, ViewerMenus].list.first.commonData.name]], level: 2];
FOR m:
LIST
OF ViewerMenu ←
NARROW[v.menus, ViewerMenus].list, m.rest
UNTIL m =
NIL
DO
IF NOT m.first.active THEN LOOP;
IF prevLineHadBreakafter
OR m.first.commonData.breakBefore
THEN {
CurrentX ← TopLeftCornerX + menuHLeading;
CurrentY ← CurrentY - ViewerSpecs.menuHeight;
};
prevLineHadBreakafter ← m.first.commonData.breakAfter;
FOR e:
LIST
OF EntryState ← m.first.entries, e.rest
UNTIL e =
NIL
DO
e.first.width ←
WITH e.first.commonData.displayData
SELECT
FROM
r: Rope.ROPE => VFonts.RopeWidth[r],
user: REF DrawingRec => user.proc[op: query, clientData: user.data]
ENDCASE => ERROR;
IF wrap
AND (v.ww > tooNarrowToWrapMenus)
THEN
IF (CurrentX + e.first.width) > (v.ww - wbs)
THEN {
wrap by just skipping down to the next line
Debug[msg: "we just decided to wrap!", level: 2];
CurrentX ← TopLeftCornerX + wrapIndent;
CurrentY ← CurrentY - ViewerSpecs.menuHeight;
};
e.first.xPos ← CurrentX;
e.first.yPos ← CurrentY;
CurrentX ← CurrentX + e.first.width + menuHSpace;
ENDLOOP;
ENDLOOP;
NARROW[v.menus, ViewerMenus].x ← TopLeftCornerX;
NARROW[v.menus, ViewerMenus].y ← CurrentY;
NARROW[v.menus, ViewerMenus].w ← v.ww - (2 * wbs);
NARROW[v.menus, ViewerMenus].h ← TopLeftCornerY - CurrentY;
Debug[msg: IO.PutFR["menu height deterimined to be: %d, a difference of: %d",IO.int[TopLeftCornerY - CurrentY], IO.int[NARROW[v.menus, ViewerMenus].h - originalHeight] ],level: 2];
RETURN[NARROW[v.menus, ViewerMenus].h - originalHeight];
};
ReComputeWindowMenus:
PUBLIC
PROC [v: Viewer, guard:
BOOL, color:
BOOL] = {
menus coordinates are computed and stored RELATIVE to the viewer
CurrentX: INTEGER ← menuHLeading + ViewerSpecs.windowBorderSize;
CurrentY: INTEGER ~ v.wh - ViewerSpecs.captionHeight; -- all have same Y coordinate
WindowManagerPrivate.windowMenus.x ← ViewerSpecs.windowBorderSize;
WindowManagerPrivate.windowMenus.y ← v.wh - ViewerSpecs.captionHeight;
WindowManagerPrivate.windowMenus.w ← v.ww - (2 * ViewerSpecs.windowBorderSize);
WindowManagerPrivate.windowMenus.h ← ViewerSpecs.captionHeight;
Debug["ReComputeWindowMenus entered", TRUE, 3];
IF guard
THEN {
Debug["ReComputeWindowMenus: guard is true", TRUE, 4];
SetDisplay["windowDestroyMenu", FALSE];
SetDisplay["windowGuardedDestroyMenu", TRUE];
}
ELSE {
Debug["ReComputeWindowMenus: guard is false", TRUE, 4];
SetDisplay["windowDestroyMenu", TRUE];
SetDisplay["windowGuardedDestroyMenu", FALSE];
};
IF color THEN SetDisplay["windowColorMenu", TRUE]
ELSE SetDisplay["windowColorMenu", FALSE];
FOR m:
LIST
OF ViewerMenu ← WindowManagerPrivate.windowMenus.list, m.rest
UNTIL m =
NIL
DO
IF NOT m.first.active THEN LOOP;
FOR e:
LIST
OF EntryState ← m.first.entries, e.rest
UNTIL e =
NIL
DO
e.first.width ←
WITH e.first.commonData.displayData
SELECT
FROM
r: Rope.ROPE => VFonts.RopeWidth[r],
user: REF DrawingRec => user.proc[op: query, clientData: user.data]
ENDCASE => ERROR;
e.first.xPos ← CurrentX;
e.first.yPos ← CurrentY;
hack for special clicking GROW and CLOSE
IF Rope.Equal[e.first.commonData.name, "Grow"]
THEN {
WindowManagerPrivate.growPos.mouseX ← e.first.xPos+1;
WindowManagerPrivate.growPos.color ← FALSE;
WindowManagerPrivate.growPos.mouseY ← e.first.yPos+1;
};
IF Rope.Equal[e.first.commonData.name, "Close"]
THEN {
WindowManagerPrivate.closePos.mouseX ← e.first.xPos+1;
WindowManagerPrivate.closePos.color ← FALSE;
WindowManagerPrivate.closePos.mouseY ← e.first.yPos+1;
};
CurrentX ← CurrentX + e.first.width + menuHSpace;
ENDLOOP;
ENDLOOP;
};
SetDisplay:
PROC [name: Rope.
ROPE, setting:
BOOL] = {
FOR l:
LIST
OF ViewerMenu ← WindowManagerPrivate.windowMenus.list, l.rest
UNTIL l =
NIL
DO
IF Rope.Equal[l.first.commonData.name, name]
THEN {
l.first.active ← setting;
RETURN;
};
ENDLOOP;
ERROR;
};
GetInternalFormatMenuRef:
PROC [name: Rope.
ROPE]
RETURNS [answer: InternalFormatMenuRef] = {
FOR m:
LIST
OF InternalFormatMenuRef ← registeredMenus, m.rest
UNTIL m =
NIL
DO
IF Rope.Equal[m.first.name, name] THEN RETURN[m.first];
ENDLOOP;
ERROR;
};
RePaintBecauseOfMenuChange:
PROC [v: Viewer] = {
ViewerBLT.ChangeMenuHeight[v, ReComputeMenus[v]];
ViewerOps.PaintViewer[viewer: v, hint: menu];
};
AlterDebuggingLevel:
PUBLIC
PROC [amount:
INTEGER, relative:
BOOL ←
TRUE] = {
string: Rope.ROPE;
IF relative THEN currentDebugLevel ← currentDebugLevel + amount
ELSE currentDebugLevel ← amount;
string ← IO.PutFR["Debugging Level Set to %d", IO.int[currentDebugLevel]];
Debug[string, TRUE, currentDebugLevel];
};
currentDebugLevel: INTEGER ← 3;
Debug:
PUBLIC
PROC [msg: Rope.
ROPE, waitForLeftShift:
BOOL ←
TRUE, level:
INTEGER ← 1] =
TRUSTED {
keys: LONG POINTER TO Interminal.KeyState = LOOPHOLE[UserTerminal.keyboard];
IF currentDebugLevel >= level
THEN {
MessageWindow.Append[msg, TRUE];
IF waitForLeftShift
THEN {
MessageWindow.Append[" ... HIT LEFT SHIFT TO CONTINUE", FALSE];
WHILE keys.bits[LeftShift]#down
DO
ENDLOOP;
WHILE keys.bits[LeftShift]#up
DO
ENDLOOP;
MessageWindow.Clear[];
};
};
}; -- Debug
ComputeFontInfo[];
UserProfile.CallWhenProfileChanges[ReEstablishUserProfileParamaters];
END.