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
Last Edited by: Wyatt, October 21, 1983 12:07 pm
DIRECTORY
Imager USING [black, Color, Context, DoSave, IntegerClipRectangle, IntegerMaskRectangle, IntegerSetXY, MakeStipple, SetColor, ShowCharacters, white],
Menus,
MenusPrivate,
MessageWindow USING [Append],
Process USING [Detach, Milliseconds, MsecToTicks, priorityNormal, SetPriority, SetTimeout],
Real USING [RoundC],
Rope USING [Equal, FromRefText, ROPE],
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, guardOffset, guardHeight, menuHeight, windowBorderSize],
WindowManager USING [RestoreCursor],
WindowManagerPrivate USING [closePos, DrawCaptionMenu, growPos, windowMenus];
MenusImpl: CEDAR MONITOR LOCKS entry USING entry: EntryState
IMPORTS Imager, MessageWindow, Process, Real, Rope, UserProfile, VFonts, ViewerBLT, ViewerOps, WindowManager, WindowManagerPrivate
EXPORTS Menus, MenusPrivate
SHARES ViewerOps
= BEGIN OPEN Menus, MenusPrivate;
ROPE: TYPE = Rope.ROPE;
Viewer: TYPE = ViewerClasses.Viewer;
menuFont: VFonts.FONT ← VFonts.defaultFont;
fontHeight: INTEGER;
baselineOffset: INTEGER;
registeredMenus: LIST OF Menu ← NIL;
widthFudge: INTEGER = 3;
targetNotFound: PUBLIC SIGNAL = CODE;
MenuNameEqual: PROC[a, b: ATOM] RETURNS[BOOL] = INLINE { RETURN[a=b] };
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: Trigger] = {
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];
};
ProcessMenuHit: ENTRY PROC [entry: EntryState,
menus: ViewerMenus, parent: Viewer, trigger: Trigger] = {
SELECT entry.guardState FROM
guarded => {
response: REF ANYNIL;
entry.guardState ← arming;
TRUSTED {Process.Detach[FORK ArmMenuProc[entry, menus, parent]]};
response ← FindAction[entry, trigger].guardResponse;
IF response#NIL THEN TRUSTED {Process.Detach[FORK GuardResponse[response]]};
};
arming=> NULL; -- no action
armed => {
IF entry.commonData.guarded THEN entry.guardState ← guarded;
MenuPusher[entry, menus, parent, trigger, FALSE];
};
ENDCASE;
};
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: Trigger,
normalPriority: BOOLTRUE] = BEGIN
changeOccurred: BOOLEANFALSE;
action: Action = FindAction[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, action.input];
entry.greyed ← FALSE;
RedrawMenu[parent, menus, entry];
changeOccurred ← FALSE;
FOR m: LIST OF MenuName ← action.makeActive, m.rest UNTIL m = NIL DO
MakeActive[viewer: parent, menu: m.first, paint: FALSE];
changeOccurred ← TRUE;
ENDLOOP;
FOR m: LIST OF MenuName ← action.makeInactive, m.rest UNTIL m = NIL DO
MakeInactive[viewer: parent, menu: m.first, paint: FALSE];
changeOccurred ← TRUE;
ENDLOOP;
FOR m: LIST OF MenuName ← action.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 => 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] = {
myGrey: Imager.Color = Imager.MakeStipple[001010B];
lowerLeftX: INTEGER ← viewer.wx + entry.xPos;
lowerLeftY: INTEGER ← viewer.wy + entry.yPos;
IF clearFirst THEN {
Imager.SetColor[context, Imager.white];
Imager.IntegerMaskRectangle[context,
lowerLeftX - widthFudge, lowerLeftY, entry.width + (2*widthFudge), fontHeight-1];
Imager.SetColor[context, Imager.black];
};
IF entry.greyed THEN {
Imager.SetColor[context, myGrey];
Imager.IntegerMaskRectangle[context,
lowerLeftX - widthFudge, lowerLeftY, entry.width + (2*widthFudge), fontHeight-1];
Imager.SetColor[context, Imager.black];
};
WITH entry.commonData.displayData SELECT FROM
r: ROPE => {
Imager.IntegerSetXY[context, lowerLeftX, lowerLeftY + baselineOffset];
Imager.ShowCharacters[context, r, VFonts.defaultFont];
};
user: REF DrawingRec => {
CallUserDrawProc: PROC = {
separate procedure because it sets a clipping rectangle
Imager.IntegerClipRectangle[context,
lowerLeftX-widthFudge, lowerLeftY, entry.width+(2*widthFudge), fontHeight-1];
[] ← user.proc[op: draw, context: context, clientData: user.data];
};
Imager.IntegerSetXY[context, lowerLeftX, lowerLeftY];
Imager.DoSave[context, CallUserDrawProc];
};
ENDCASE => ERROR;
IF entry.commonData.guarded AND entry.guardState # armed THEN { OPEN ViewerSpecs;
Imager.IntegerMaskRectangle[context,
lowerLeftX - 1, lowerLeftY + baselineOffset+guardOffset, entry.width + 2, guardHeight];
};
};
DrawMenu: PUBLIC PROC [v: Viewer, menus: ViewerMenus, context: Imager.Context, whatChanged: REF ANYNIL] = {
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];
WITH whatChanged SELECT FROM
entry: EntryState => DrawSingleEntry[v, context, entry];
ENDCASE => {
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;
};
};
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;
FindAction: PROC[entry: EntryState, trigger: Trigger] RETURNS [Action] = {
FOR list: LIST OF Action ← entry.commonData.actions, list.rest UNTIL list=NIL DO
action: Action = list.first;
IF action.triggers[trigger] THEN RETURN[action];
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];
};
ValidateMenu: PROC[menu: Menu] = {
FOR entries: LIST OF Entry ← menu.entries, entries.rest UNTIL entries=NIL DO
entry: Entry = entries.first;
IF entry.displayData=NIL THEN entry.displayData ← entry.name
ELSE {
This is here so that user-created entries that have displayData fields of type REF TEXT can be converted to ROPE. If the compiler ever defaults quoted text strings, this can be safely removed.
WITH entry.displayData SELECT FROM
dispData: REF TEXT => entry.displayData ← Rope.FromRefText[dispData];
okRope: ROPE => NULL;
okProc: REF DrawingRec => NULL;
ENDCASE => ERROR; -- invalid data type for DisplayData field.
};
FOR actions: LIST OF Action ← entry.actions, actions.rest UNTIL actions=NIL DO
action: Action = actions.first;
IF action.guardResponse=NIL THEN NULL
ELSE {
This is here so that user-created entries that have displayData fields of type REF TEXT can be converted to ROPE. If the compiler ever defaults quoted text strings, this can be safely removed.
WITH action.guardResponse SELECT FROM
textRef: REF TEXT => action.guardResponse ← Rope.FromRefText[textRef];
okRope: ROPE => NULL;
okRec: REF UnGuardRec => NULL;
ENDCASE => ERROR; -- invalid data type for guardResponse field.
};
ENDLOOP;
ENDLOOP;
};
RegisterMenu: PUBLIC PROC [menu: Menu] = {
ValidateMenu[menu];
registeredMenus ← CONS[menu, registeredMenus];
};
AlreadyRegistered: PUBLIC PROC [name: MenuName] RETURNS [registered: BOOLEAN] = {
FOR m: LIST OF Menu ← registeredMenus, m.rest UNTIL m = NIL DO
IF MenuNameEqual[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]
menus: ViewerMenus = NARROW[v.menus];
FOR m: LIST OF ViewerMenu ← menus.list, m.rest UNTIL m = NIL DO
IF MenuNameEqual[menu.name, m.first.commonData.name] THEN m.first ← NEW[ViewerMenuRec ← newMenu];
ENDLOOP;
IF paint THEN RePaintBecauseOfMenuChange[v];
};
ValidateMenu[menu];
FOR m: LIST OF Menu ← registeredMenus, m.rest UNTIL m = NIL DO
IF MenuNameEqual[m.first.name, menu.name] THEN {
m.first ← menu;
newMenu ← MakeNewViewerMenuRec[m.first];
ViewerOps.EnumerateViewers[MenuDefinitonChanged];
RETURN;
};
ENDLOOP;
ERROR targetNotFound;
};
GetRegisteredMenu: PUBLIC PROC [name: MenuName] RETURNS [Menu] = {
FOR m: LIST OF Menu ← registeredMenus, m.rest UNTIL m = NIL DO
menu: Menu = m.first;
IF MenuNameEqual[menu.name, name] THEN RETURN[menu];
ENDLOOP;
ERROR targetNotFound;
};
ClearMenus: PUBLIC PROC [viewer: Viewer, paint: BOOLTRUE] = {
viewer.menus ← NIL;
IF paint THEN RePaintBecauseOfMenuChange[viewer];
};
AddMenu: PUBLIC PROC [viewer: Viewer, name: MenuName, paint: BOOLTRUE, addBefore: MenuName ← NIL] = {
menus: ViewerMenus = NARROW[viewer.menus];
IF menus = NIL THEN {
IF addBefore # NIL THEN SIGNAL targetNotFound;
viewer.menus ← NEW[ViewerMenusRec ← [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 ← menus.list, m.rest UNTIL m = NIL DO
IF MenuNameEqual[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 GetRegisteredMenu[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 ← menus.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 ← menus.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: MenuName, addBefore: MenuName ← NIL, 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 MenuNameEqual[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 GetRegisteredMenu[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: MenuName] RETURNS[ViewerMenu] = {
FOR m: LIST OF Menu ← registeredMenus, m.rest UNTIL m = NIL DO
IF MenuNameEqual[m.first.name, name] THEN
RETURN[NEW[ViewerMenuRec ← MakeNewViewerMenuRec[m.first]]];
ENDLOOP;
ERROR targetNotFound;
};
MakeNewViewerMenuRec: PROC[menu: Menu] RETURNS[ViewerMenuRec] = {
RETURN[[commonData: menu, active: menu.beginsActive,
entries: MakeNewViewerMenuEntries[menu]]];
};
MakeNewViewerMenuEntries: PROC[menu: Menu] RETURNS[LIST OF EntryState] = {
reversedList: LIST OF EntryState ← NIL;
answer: LIST OF EntryState ← NIL;
FOR e: LIST OF Entry ← 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: MenuName, paint: BOOLTRUE] = {
menus: ViewerMenus = NARROW[viewer.menus];
FOR m: LIST OF ViewerMenu ← menus.list, m.rest UNTIL m = NIL DO
IF MenuNameEqual[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: MenuName, paint: BOOLTRUE] = {
menus: ViewerMenus = NARROW[viewer.menus];
FOR m: LIST OF ViewerMenu ← menus.list, m.rest UNTIL m = NIL DO
IF MenuNameEqual[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: MenuName, paint: BOOLTRUE] = {
menus: ViewerMenus = NARROW[viewer.menus];
FOR m: LIST OF ViewerMenu ← menus.list, m.rest UNTIL m = NIL DO
IF MenuNameEqual[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: MenuName] RETURNS[exists: BOOLEAN] = {
menus: ViewerMenus = NARROW[viewer.menus];
FOR m: LIST OF ViewerMenu ← menus.list, m.rest UNTIL m = NIL DO
IF MenuNameEqual[m.first.commonData.name, menu] THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
};
The user-profilible menu options:
wrap: BOOLEANTRUE; -- TRUE iff user wants menu entries ALWAYS to 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
This routine is called whenever the user profile changes:
ReEstablishUserProfileParameters: 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] = {
OPEN ViewerSpecs;
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
menus: ViewerMenus = NARROW[v.menus];
originalHeight: INTEGER;
prevLineHadBreakafter: BOOLEANTRUE; -- starting true forces first line to move down
wbs: INTEGERIF v.border THEN windowBorderSize ELSE 0;
TopLeftCornerX: INTEGER ← wbs;
TopLeftCornerY: INTEGER ← v.wh-captionHeight;
CurrentX: INTEGER ← TopLeftCornerX + menuHLeading;
CurrentY: INTEGER ← TopLeftCornerY;
IF menus=NIL OR v.iconic THEN RETURN[0];
originalHeight ← menus.h;
IF wrap THEN Debug[msg: IO.PutFR["RecomputeMenus for %g ==> wrap is true", IO.rope[ menus.list.first.commonData.name]], level: 2]
ELSE Debug[msg: IO.PutFR["RecomputeMenus for %g ==> wrap is false", IO.rope[menus.list.first.commonData.name]], level: 2];
FOR m: LIST OF ViewerMenu ← menus.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 - 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 => 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 - menuHeight;
};
e.first.xPos ← CurrentX;
e.first.yPos ← CurrentY;
CurrentX ← CurrentX + e.first.width + menuHSpace;
ENDLOOP;
ENDLOOP;
menus.x ← TopLeftCornerX;
menus.y ← CurrentY;
menus.w ← v.ww - (2 * wbs);
menus.h ← TopLeftCornerY - CurrentY;
Debug[msg: IO.PutFR["menu height deterimined to be: %d, a difference of: %d",IO.int[TopLeftCornerY - CurrentY], IO.int[menus.h - originalHeight] ],level: 2];
RETURN[menus.h - originalHeight];
};
ReComputeWindowMenus: PUBLIC PROC [v: Viewer, guard: BOOL, color: BOOL] = {
OPEN ViewerSpecs;
menus coordinates are computed and stored RELATIVE to the viewer
CurrentX: INTEGER ← menuHLeading + windowBorderSize;
CurrentY: INTEGER ~ v.wh - captionHeight; -- all have same Y coordinate
WindowManagerPrivate.windowMenus.x ← windowBorderSize;
WindowManagerPrivate.windowMenus.y ← v.wh - captionHeight;
WindowManagerPrivate.windowMenus.w ← v.ww - (2 * windowBorderSize);
WindowManagerPrivate.windowMenus.h ← 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 => 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: MenuName, setting: BOOL] = {
FOR l: LIST OF ViewerMenu ← WindowManagerPrivate.windowMenus.list, l.rest UNTIL l = NIL DO
IF MenuNameEqual[l.first.commonData.name, name] THEN {
l.first.active ← setting;
RETURN;
};
ENDLOOP;
ERROR;
};
RePaintBecauseOfMenuChange: PROC[v: Viewer] = {
ViewerBLT.ChangeMenuHeight[v, RecomputeMenus[v]];
ViewerOps.PaintViewer[viewer: v, hint: menu];
};
ComputeFontInfo[];
UserProfile.CallWhenProfileChanges[ReEstablishUserProfileParameters];
END.
currentDebugLevel: INTEGER ← 3;
AlterDebuggingLevel: PUBLIC PROC [amount: INTEGER, relative: BOOLTRUE] = {
string: 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];
};
Debug: PUBLIC PROC [msg: 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