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 ANY ← NIL;
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: BOOL ← TRUE] = BEGIN
changeOccurred: BOOLEAN ← FALSE;
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:
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] = {
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
ANY ←
NIL] = {
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:
BOOL ←
TRUE] = {
newMenu: ViewerMenuRec;
MenuDefinitonChanged: ViewerOps.EnumProc = {
[v: Viewer] RETURNS [BOOL ← TRUE]
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:
BOOL ←
TRUE] = {
viewer.menus ← NIL;
IF paint THEN RePaintBecauseOfMenuChange[viewer];
};
AddMenu:
PUBLIC
PROC [viewer: Viewer, name: MenuName, paint:
BOOL ←
TRUE, 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: BOOLEAN ← FALSE;
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:
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 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:
BOOL ←
TRUE] = {
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:
BOOL ←
TRUE] = {
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:
BOOL ←
TRUE] = {
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: BOOLEAN ← TRUE; -- 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: BOOLEAN ← TRUE; -- starting true forces first line to move down
wbs: INTEGER ← IF 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: BOOL ← TRUE] = {
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: 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