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: BOOLTRUE] = BEGIN
changeOccurred: BOOLEANFALSE;
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: BOOLTRUE] =
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: BOOLEANTRUE] = {
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 ANYNIL] = 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: BOOLTRUE] = {
newMenu: ViewerMenuRec;
MenuDefinitonChanged: ViewerOps.EnumProc = {
[v: viewer] RETURNS [BOOLTRUE]
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: BOOLTRUE] = {
viewer.menus ← NIL;
IF paint THEN RePaintBecauseOfMenuChange[viewer];
};
AddMenu: PUBLIC PROC [viewer: Viewer, name: Rope.ROPE, paint: BOOLTRUE, addBefore: Rope.ROPENIL] = {
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: BOOLEANFALSE;
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.ROPENIL, paint: BOOLTRUE] = {
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: BOOLEANFALSE;
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: BOOLTRUE] = {
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: BOOLTRUE] = {
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: BOOLTRUE] = {
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: BOOLEANTRUE; -- 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: BOOLEANTRUE; -- starting true forces first line to move down
wbs: INTEGERIF 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: BOOLTRUE] = {
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: BOOLTRUE, 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.