<> <> <> <> <> DIRECTORY Imager USING [black, Color, Context, IntegerMaskRectangle, IntegerSetXY, MakeStipple, SetColor, ShowCharacters, white], Menus, MenusPrivate, MessageWindow USING [Append], Process USING [Detach, Milliseconds, MsecToTicks, priorityNormal, SetPriority, SetTimeout], Real USING [RoundC], Rope USING [ROPE], TIPUser USING [TIPScreenCoords], UserProfile USING [Boolean, CallWhenProfileChanges, Number, ProfileChangedProc], VFonts USING [defaultFont, FONT, FontAscent, FontHeight, RopeWidth], ViewerBLT USING [ChangeHeader], ViewerClasses, ViewerOps USING [HelpViewer, InvertForMenus, NotifyViewer, PaintViewer], ViewerSpecs USING [captionHeight, guardOffset, guardHeight, menuBarHeight, menuHeight, windowBorderSize], WindowManager USING [RestoreCursor], WindowManagerPrivate USING [DrawCaptionMenu, ProcessWindowResults, windowMenu]; MenusImpl: CEDAR MONITOR LOCKS entry USING entry: EntryInfo IMPORTS Imager, MessageWindow, Process, Real, UserProfile, VFonts, ViewerBLT, ViewerOps, WindowManager, WindowManagerPrivate EXPORTS Menus, MenusPrivate SHARES ViewerOps = BEGIN OPEN Menus, MenusPrivate; Viewer: TYPE = ViewerClasses.Viewer; ROPE: TYPE = Rope.ROPE; menuFont: VFonts.FONT _ VFonts.defaultFont; fontHeight: INTEGER; baselineOffset: INTEGER; SetMenu: PUBLIC PROC[viewer: Viewer, menu: Menu, paint: BOOL _ TRUE] = { viewer.menu _ MakeMenu[menu]; }; MakeEntry: PUBLIC PROC[entry: Entry] RETURNS[EntryInfo] = { RETURN[NEW[MenusPrivate.EntryInfoRec _ [name: entry.name, actions: entry.actions, guarded: entry.guarded, state: IF entry.guarded THEN guarded ELSE armed]]]; }; MakeMenu: PROC[menu: Menu] RETURNS[MenuInfo] = { CopyEntries: PROC[list: LIST OF Entry] RETURNS[LIST OF EntryInfo] = { RETURN[IF list=NIL THEN NIL ELSE CONS[MakeEntry[list.first], CopyEntries[list.rest]]]; }; MakeLine: PROC[line: Line] RETURNS[LineInfo] = { RETURN[NEW[LineInfoRec _ [name: line.name, entries: CopyEntries[line.entries], active: line.active]]]; }; CopyLines: PROC[list: LIST OF Line] RETURNS[LIST OF LineInfo] = { RETURN[IF list=NIL THEN NIL ELSE CONS[MakeLine[list.first], CopyLines[list.rest]]]; }; RETURN[NEW[MenuInfoRec _ [lines: CopyLines[menu]]]]; }; widthFudge: INTEGER = 3; MarkMenu: PUBLIC PROC[menu: MenuInfo, parent: Viewer, mousePos: TIPUser.TIPScreenCoords] = { entry: EntryInfo = ResolveEntry[menu, mousePos]; IF menu.inverted#entry THEN { IF menu.inverted#NIL THEN InvertEntry[menu, parent]; menu.inverted _ entry; IF menu.inverted#NIL THEN InvertEntry[menu, parent]; }; }; HitMenu: PUBLIC PROC[menu: MenuInfo, parent: Viewer, mousePos: TIPUser.TIPScreenCoords, trigger: Trigger] = { entry: EntryInfo = ResolveEntry[menu, mousePos]; hit: BOOL = (menu.inverted#NIL AND entry=menu.inverted); IF menu.inverted#NIL THEN InvertEntry[menu, parent]; -- take down menu.inverted _ NIL; IF hit THEN ProcessMenuHit[entry, menu, parent, trigger]; }; ProcessMenuHit: ENTRY PROC[entry: EntryInfo, menus: MenuInfo, parent: Viewer, trigger: Trigger] = { SELECT entry.state FROM guarded => { entry.state _ arming; TRUSTED {Process.Detach[FORK ArmMenuProc[entry, menus, parent]]}; TRUSTED {Process.Detach[FORK GuardResponse[parent, ChooseAction[entry, trigger]]]}; }; arming=> NULL; -- no action armed => { IF entry.guarded THEN entry.state _ guarded; MenuPusher[entry, menus, parent, trigger, FALSE]; }; ENDCASE; }; ArmMenuProc: ENTRY PROC[entry: EntryInfo, menus: MenuInfo, parent: Viewer] = { menuWaitCondition: CONDITION; <> TRUSTED {Process.SetTimeout[@menuWaitCondition, Process.MsecToTicks[armingTime]]}; WAIT menuWaitCondition; IF entry.state = arming THEN { entry.state _ armed; RedrawMenu[parent, menus, entry]; TRUSTED {Process.SetTimeout[@menuWaitCondition, Process.MsecToTicks[armedTime]]}; WAIT menuWaitCondition; }; IF entry.state#guarded THEN { entry.state _ guarded; RedrawMenu[parent, menus, entry]; }; }; GuardResponse: PUBLIC PROC[viewer: Viewer, action: Action] = { IF ViewerOps.HelpViewer[viewer, action.input] THEN NULL ELSE MessageWindow.Append[action.doc, TRUE]; }; MenuPusher: PROC[entry: EntryInfo, menu: MenuInfo, parent: Viewer, trigger: Trigger, normalPriority: BOOL _ TRUE] = { action: Action = ChooseAction[entry, trigger]; entry.greyCount _ entry.greyCount+1; IF entry.greyCount=1 THEN RedrawMenu[parent, menu, entry]; IF normalPriority THEN TRUSTED {Process.SetPriority[Process.priorityNormal]}; IF menu=WindowManagerPrivate.windowMenu THEN [] _ WindowManagerPrivate.ProcessWindowResults[parent, action.input] ELSE ViewerOps.NotifyViewer[parent, action.input]; entry.greyCount _ entry.greyCount-1; IF entry.greyCount=0 THEN RedrawMenu[parent, menu, entry]; WindowManager.RestoreCursor[]; }; RedrawMenu: PROC[viewer: Viewer, menu: MenuInfo, entry: EntryInfo] = { IF menu=WindowManagerPrivate.windowMenu THEN WindowManagerPrivate.DrawCaptionMenu[viewer, FALSE] ELSE ViewerOps.PaintViewer[viewer: viewer, hint: menu, whatChanged: entry]; }; ClearMenu: PUBLIC PROC[menu: MenuInfo, parent: Viewer, paint: BOOL _ TRUE] = { IF menu.inverted#NIL THEN { IF paint THEN InvertEntry[menu, parent]; menu.inverted _ NIL; }; }; myGrey: Imager.Color = Imager.MakeStipple[001010B]; DrawSingleEntry: PROC[viewer: Viewer, context: Imager.Context, entry: EntryInfo, clearFirst: BOOLEAN _ TRUE] = { x: INTEGER = viewer.wx + entry.x; y: INTEGER = viewer.wy + entry.y; w: INTEGER = entry.w; IF clearFirst THEN { Imager.SetColor[context, Imager.white]; Imager.IntegerMaskRectangle[context, x-widthFudge, y, w+(2*widthFudge), fontHeight-1]; }; IF entry.greyCount>0 THEN { Imager.SetColor[context, myGrey]; Imager.IntegerMaskRectangle[context, x-widthFudge, y, w+(2*widthFudge), fontHeight-1]; }; Imager.SetColor[context, Imager.black]; Imager.IntegerSetXY[context, x, y+baselineOffset]; Imager.ShowCharacters[context, entry.name, VFonts.defaultFont]; IF entry.guarded AND entry.state#armed THEN { OPEN ViewerSpecs; Imager.IntegerMaskRectangle[context, x-1, y+baselineOffset+guardOffset, w+2, guardHeight]; }; }; DrawMenu: PUBLIC PROC[v: Viewer, menu: MenuInfo, context: Imager.Context, whatChanged: REF ANY _ NIL] = { <> WITH whatChanged SELECT FROM entry: EntryInfo => { DrawSingleEntry[v, context, entry]; RETURN }; ENDCASE; FOR lines: LIST OF LineInfo _ menu.lines, lines.rest UNTIL lines=NIL DO line: LineInfo = lines.first; IF NOT line.active THEN LOOP; FOR entries: LIST OF EntryInfo _ line.entries, entries.rest UNTIL entries=NIL DO DrawSingleEntry[v, context, entries.first, FALSE]; ENDLOOP; ENDLOOP; menu.inverted _ NIL; }; <> cacheMenu: MenuInfo _ NIL; cacheLine: LineInfo _ NIL; cacheEntry: EntryInfo _ NIL; ResolveEntry: PROC[menu: MenuInfo, mousePos: TIPUser.TIPScreenCoords] RETURNS[EntryInfo] = { x: INTEGER = mousePos.mouseX; y: INTEGER = mousePos.mouseY; <> IF cacheMenu=menu AND cacheLine.active AND x IN[cacheEntry.x..cacheEntry.x+cacheEntry.w) AND y IN[cacheEntry.y..cacheEntry.y+fontHeight) THEN RETURN[cacheEntry]; <> FOR lines: LIST OF LineInfo _ menu.lines, lines.rest UNTIL lines=NIL DO line: LineInfo = lines.first; IF NOT line.active THEN LOOP; FOR entries: LIST OF EntryInfo _ line.entries, entries.rest UNTIL entries=NIL DO entry: EntryInfo = entries.first; IF x IN[entry.x..entry.x+entry.w) AND y IN[entry.y..entry.y+fontHeight) THEN { cacheMenu _ menu; cacheLine _ line; cacheEntry _ entry; RETURN[entry] }; ENDLOOP; ENDLOOP; RETURN[NIL]; }; InvertEntry: PROC[menus: MenuInfo, parent: Viewer] = { IF NOT(parent.destroyed OR parent.iconic) THEN { entry: EntryInfo _ menus.inverted; ViewerOps.InvertForMenus[parent, (parent.wx+entry.x)-widthFudge, parent.wy+entry.y, entry.w+(2*widthFudge), fontHeight-1]; }; }; ComputeFontInfo: PROC = { ascent: INTEGER = VFonts.FontAscent[menuFont]; fontHeight _ VFonts.FontHeight[menuFont]; baselineOffset _ Real.RoundC[fontHeight-ascent]; }; ChooseAction: PROC[entry: EntryInfo, trigger: Trigger] RETURNS[Action] = { list: LIST OF Action _ entry.actions; count: NAT _ 0; IF list=NIL THEN RETURN[[NIL, NIL, NIL]]; WHILE count> <> <> <<-- [v: Viewer] RETURNS [BOOL _ TRUE] -->> <> <> <> <> <> <<};>> <> <> <> <> <> <> <> <<};>> <> <> <<};>> <<>> <> <> <> <> <> <> <<};>> <<>> <> <> <> <<};>> <<>> <> <> <> <> <> <<}>> <> <> <> <> <> <> <> <> <<};>> <> <> <> <<}>> <> <> <> <> <> <> <> <<}>> <> <> <> <> <> <> <> <<};>> <> <<};>> <<>> <> <> <> <> <> <> <<}>> <> <> <> <> <> <> <> <> <<};>> <> <> <> <<}>> <> <> <> <> <> <> <> <<}>> <> <> <> <> <> <> <> <<};>> <<};>> <<>> ChangeLine: PUBLIC PROC[viewer: Viewer, name: ATOM, change: Change] = { menu: MenuInfo = NARROW[viewer.menu]; IF menu=NIL THEN RETURN; FOR lines: LIST OF LineInfo _ menu.lines, lines.rest UNTIL lines=NIL DO line: LineInfo = lines.first; wasActive: BOOL = line.active; IF line.name=name THEN { SELECT change FROM show => line.active _ TRUE; hide => line.active _ FALSE; toggle => line.active _ NOT line.active; ENDCASE; IF line.active#wasActive THEN ViewerBLT.ChangeHeader[viewer]; RETURN; }; ENDLOOP; }; LineInViewer: PUBLIC PROC[viewer: Viewer, name: ATOM] RETURNS[exists: BOOLEAN] = { WITH viewer.menu SELECT FROM menu: MenuInfo => { FOR lines: LIST OF LineInfo _ menu.lines, lines.rest UNTIL lines=NIL DO line: LineInfo = lines.first; IF line.name=name THEN RETURN[TRUE]; ENDLOOP; }; ENDCASE => IF viewer.menu#NIL THEN ERROR; RETURN[FALSE]; }; <> wrap: BOOLEAN _ TRUE; -- TRUE iff user wants menu entries ALWAYS to be displayed wrapIndent: INTEGER _ (menuHLeading*4); -- indentation on non-first lines of wrapped menu tooNarrowToWrapMenus: INTEGER _ 150; -- don't wrap if the window is ridiculously narrow <> ReEstablishUserProfileParameters: PUBLIC UserProfile.ProfileChangedProc = { wrap _ UserProfile.Boolean[key: "ViewerMenusWrap", default: TRUE]; wrapIndent _ UserProfile.Number[key: "ViewerMenusWrapIndent", default: (menuHLeading*4)]; tooNarrowToWrapMenus _ UserProfile.Number["ViewerMenusTooNarrowToWrap", 150]; }; EstablishHeader: PUBLIC PROC[v: Viewer] RETURNS[y: INTEGER] = { OPEN ViewerSpecs; vbs: INTEGER = IF v.border THEN windowBorderSize ELSE 0; ch: INTEGER = IF v.parent=NIL AND v.column#static THEN captionHeight ELSE vbs; WITH v.menu SELECT FROM menu: MenuInfo => { RecomputeMenu[menu: menu, xmin: vbs, xmax: v.ww-vbs, ymax: v.wh-ch]; RETURN[menu.y-menuBarHeight]; }; ENDCASE => IF v.menu=NIL THEN RETURN[v.wh-ch] ELSE ERROR; }; RecomputeMenu: PROC[menu: MenuInfo, xmin, xmax, ymax: INTEGER] = { OPEN ViewerSpecs; <> wrappingOK: BOOL _ (wrap AND (xmax-xmin)>tooNarrowToWrapMenus); y: INTEGER _ ymax; FOR lines: LIST OF LineInfo _ menu.lines, lines.rest UNTIL lines=NIL DO line: LineInfo = lines.first; x: INTEGER _ xmin + menuHLeading; IF NOT line.active THEN LOOP; y _ y - menuHeight; -- begin new line FOR e: LIST OF EntryInfo _ line.entries, e.rest UNTIL e = NIL DO entry: EntryInfo = e.first; w: INTEGER = VFonts.RopeWidth[entry.name]; IF (x+w)>xmax AND wrappingOK THEN { x _ xmin+wrapIndent; y _ y-menuHeight; -- move down a line and indent }; entry.x _ x; entry.y _ y; entry.w _ w; x _ x + w + menuHSpace; ENDLOOP; ENDLOOP; menu.x _ xmin; menu.y _ y; menu.w _ xmax-xmin; menu.h _ ymax-y; }; <> <> <> <> <> <> <> <> <> <<>> <> <> <> <<}>> <> <> <> <<};>> <> <> <<>> <> <> <> <> < VFonts.RopeWidth[r],>> < user.proc[op: query, clientData: user.data]>> < ERROR;>> <> <> <> <> <> <> <> <<};>> <> <> <> <> <<};>> <> <> <> <<};>> <<>> ComputeFontInfo[]; UserProfile.CallWhenProfileChanges[ReEstablishUserProfileParameters]; END.