<<>> <> <> <> <> <> <> <<>> DIRECTORY CedarProcess USING [SetPriority], Imager USING [black, Color, Context, MaskRectangleI, SetColor, SetFont, SetXRelI, SetXYI, ShowRope, white], ImagerFont USING [Extents, FontBoundingBox], ImagerBackdoor USING [MakeStipple], Menus USING [armedTime, armingTime, ClickProc, Menu, MenuEntry, MenuEntryRec, MenuRec, MenuLine, MouseButton], MessageWindow USING [Append], Process USING [Detach, Milliseconds, MsecToTicks, SetTimeout], Real USING [Round], Rope USING [Equal, FromRefText, ROPE], TIPUser USING [TIPScreenCoords], VFonts USING [defaultFont, Font, StringWidth], ViewerClasses USING [Viewer], ViewerOps USING [PaintViewer], ViewerPrivate USING [DrawCaptionMenu, InvertForMenus, menuHLeading, menuHSpace, windowMenu], ViewerSpecs USING [menuHeight], WindowManager USING [RestoreCursor]; MenusImpl: CEDAR MONITOR LOCKS entry USING entry: MenuEntry IMPORTS CedarProcess, Imager, ImagerBackdoor, ImagerFont, MessageWindow, Process, Real, Rope, VFonts, ViewerOps, ViewerPrivate, ViewerSpecs, WindowManager EXPORTS Menus, ViewerPrivate SHARES ViewerClasses, ViewerOps = BEGIN OPEN Menus; Viewer: TYPE ~ ViewerClasses.Viewer; menuFont: VFonts.Font ¬ VFonts.defaultFont; fontHeight: INTEGER ¬ 0; baselineOffset: INTEGER ¬ 0; margin: INTEGER = 3; CreateMenu: PUBLIC PROC [lines: MenuLine ¬ 1] RETURNS [menu: Menu] ~ { RETURN[NEW[MenuRec ¬ [linesUsed: lines]]] }; CopyMenu: PUBLIC PROC [old: Menu] RETURNS [new: Menu] = { new ¬ CreateMenu[old.linesUsed]; FOR l: MenuLine IN MenuLine UNTIL old.lines[l]=NIL DO new.lines[l] ¬ old.lines[l]; CopyLine[new, l]; ENDLOOP; }; <> <> <> <> <> <> <> <> <> <> <> <> <<};>> <<>> CopyLine: PROC [menu: Menu, line: INTEGER] = { oldEntry: MenuEntry ¬ menu.lines[line]; newEntry: MenuEntry; menu.lines[line] ¬ NIL; FOR t: MenuEntry ¬ oldEntry, t.link UNTIL t=NIL DO IF menu.lines[line]=NIL THEN newEntry ¬ menu.lines[line] ¬ CopyEntry[t] ELSE newEntry ¬ newEntry.link ¬ CopyEntry[t]; newEntry.greyed ¬ FALSE; newEntry.guardState ¬ IF newEntry.guarded THEN guarded ELSE armed; ENDLOOP; }; CopyEntry: PUBLIC PROC [oldEntry: MenuEntry] RETURNS [newEntry: MenuEntry] = { newEntry ¬ NEW[MenuEntryRec]; newEntry.link ¬ oldEntry.link; newEntry.name ¬ oldEntry.name; newEntry.proc ¬ oldEntry.proc; newEntry.width ¬ oldEntry.width; newEntry.xPos ¬ oldEntry.xPos; newEntry.clientData ¬ oldEntry.clientData; newEntry.documentation ¬ oldEntry.documentation; newEntry.fork ¬ oldEntry.fork; newEntry.greyed ¬ oldEntry.greyed; newEntry.guarded ¬ oldEntry.guarded; newEntry.guardState ¬ oldEntry.guardState; }; CreateEntry: PUBLIC PROC [name: Rope.ROPE, proc: ClickProc, clientData: REF ANY ¬ NIL, documentation: REF ANY ¬ NIL, fork: BOOL ¬ TRUE, guarded: BOOL ¬ FALSE] RETURNS [entry: MenuEntry] = { entry ¬ NEW[MenuEntryRec ¬ [ name: name, proc: proc, clientData: clientData, documentation: documentation, fork: fork, guarded: guarded, guardState: IF guarded THEN guarded ELSE armed, width: VFonts.StringWidth[name, menuFont] ]]; }; SetGuarded: PUBLIC PROC [entry: MenuEntry, guard: BOOL] = { entry.guarded ¬ guard; entry.guardState ¬ IF guard THEN guarded ELSE armed; }; SetClientData: PUBLIC PROC [entry: MenuEntry, newData: REF ANY] RETURNS [oldData: REF ANY] = { oldData ¬ entry.clientData; entry.clientData ¬ newData; }; SetDocumentation: PUBLIC PROC [entry: MenuEntry, newDoc: REF ANY] RETURNS [oldDoc: REF ANY] = { oldDoc ¬ entry.documentation; entry.documentation ¬ newDoc; }; FindEntry: PUBLIC PROC [menu: Menu, entryName: Rope.ROPE] RETURNS [entry: MenuEntry] = { IF menu=NIL THEN RETURN[NIL]; FOR l: MenuLine IN MenuLine UNTIL menu.lines[l]=NIL DO FOR e: MenuEntry ¬ menu.lines[l], e.link UNTIL e=NIL DO IF Rope.Equal[e.name, entryName] THEN RETURN[e]; ENDLOOP; ENDLOOP; }; InsertMenuEntry: PUBLIC PROC [menu: Menu, entry: MenuEntry, line: MenuLine ¬ 0] = { entry.link ¬ menu.lines[line]; menu.lines[line] ¬ entry; entry.xPos ¬ ViewerPrivate.menuHLeading; FOR e: MenuEntry ¬ menu.lines[line].link, e.link UNTIL e=NIL DO e.xPos ¬ e.xPos + entry.width + ViewerPrivate.menuHSpace; ENDLOOP; }; AppendMenuEntry: PUBLIC PROC [menu: Menu, entry: MenuEntry, line: MenuLine ¬ 0] = { entry.link ¬ NIL; IF menu.lines[line]=NIL THEN {entry.xPos ¬ ViewerPrivate.menuHLeading; menu.lines[line] ¬ entry} ELSE FOR e: MenuEntry ¬ menu.lines[line], e.link UNTIL e.link=NIL DO REPEAT FINISHED => { entry.xPos ¬ e.xPos+e.width+ViewerPrivate.menuHSpace; e.link ¬ entry}; ENDLOOP; }; MenuFromMenuEntry: PUBLIC PROC[entryList: MenuEntry] RETURNS [Menu] ~ { menu: Menu ~ CreateMenu[lines: 1]; SetLine[menu: menu, line: 0, entryList: entryList]; RETURN [menu]; }; MenuEntryFind: PUBLIC PROC[entryName: Rope.ROPE, entryList: MenuEntry] RETURNS [MenuEntry] ~ { <> menu: Menu ~ MenuFromMenuEntry[entryList: entryList]; RETURN [FindEntry[menu: menu, entryName: entryName]]; }; <<>> MenuEntryAppend: PUBLIC PROC[entry: MenuEntry, entryList: MenuEntry] RETURNS [MenuEntry] ~ { <> menu: Menu ~ MenuFromMenuEntry[entryList: entryList]; AppendMenuEntry[menu: menu, entry: entry, line: 0]; RETURN [GetLine[menu: menu, line: 0]]; }; MenuEntryInsert: PUBLIC PROC[entry: MenuEntry, entryList: MenuEntry] RETURNS [MenuEntry] ~ { <> menu: Menu ~ MenuFromMenuEntry[entryList: entryList]; InsertMenuEntry[menu: menu, entry: entry, line: 0]; RETURN [GetLine[menu: menu, line: 0]]; }; <<>> MenuEntryReplace: PUBLIC PROC[entryList: MenuEntry, old: MenuEntry, new: MenuEntry] RETURNS [MenuEntry] ~ { <> <> menu: Menu ~ MenuFromMenuEntry[entryList: entryList]; ReplaceMenuEntry[menu: menu, oldEntry: old, newEntry: new]; < RAISE targetNotFound>> RETURN [GetLine[menu: menu, line: 0]]; }; targetNotFound: PUBLIC SIGNAL = CODE; ReplaceMenuEntry: PUBLIC PROC [menu: Menu, oldEntry: MenuEntry, newEntry: MenuEntry ¬ NIL] = { l: MenuLine; x: INTEGER ¬ ViewerPrivate.menuHLeading; exit: BOOL ¬ FALSE; FOR l IN MenuLine UNTIL menu.lines[l]=NIL DO IF oldEntry=menu.lines[l] THEN { IF newEntry=NIL THEN menu.lines[l] ¬ menu.lines[l].link ELSE { newEntry.link ¬ oldEntry.link; menu.lines[l] ¬ newEntry; }; EXIT; } ELSE FOR e: MenuEntry ¬ menu.lines[l], e.link UNTIL e.link=NIL DO IF e.link=oldEntry THEN { IF newEntry=NIL THEN e.link ¬ e.link.link ELSE { newEntry.link ¬ oldEntry.link; e.link ¬ newEntry; }; exit ¬ TRUE; EXIT; }; ENDLOOP; IF exit THEN EXIT; REPEAT FINISHED => SIGNAL targetNotFound; ENDLOOP; IF menu.lines[l]#NIL THEN menu.lines[l].xPos ¬ ViewerPrivate.menuHLeading; FOR e: MenuEntry ¬ menu.lines[l], e.link UNTIL e=NIL DO e.xPos ¬ x; x ¬ x+e.width+ViewerPrivate.menuHSpace; ENDLOOP; }; ChangeNumberOfLines: PUBLIC PROC [menu: Menu, newLines: MenuLine] = { menu.linesUsed ¬ newLines; }; GetNumberOfLines: PUBLIC PROC [menu: Menu] RETURNS [lines: MenuLine] = { RETURN[menu.linesUsed]; }; SetLine: PUBLIC PROC [menu: Menu, line: MenuLine, entryList: MenuEntry] = { menu.lines[line] ¬ entryList; menu.linesUsed ¬ MAX[menu.linesUsed, line+1]; }; GetLine: PUBLIC PROC [menu: Menu, line: MenuLine] RETURNS [entryList: MenuEntry] = { RETURN[menu.lines[line]]; }; MarkMenu: PUBLIC PROC [menu: Menu, parent: ViewerClasses.Viewer, mousePos: TIPUser.TIPScreenCoords] = { delta: INTEGER ~ (menu.y-mousePos.mouseY); IF delta >= 0 THEN { row: NAT ~ NAT[delta]/ViewerSpecs.menuHeight; newEntry: MenuEntry ¬ ResolveEntry[menu, row, mousePos.mouseX]; SELECT TRUE FROM newEntry=NIL => { IF menu.inverted#NIL THEN InvertEntry[menu, parent]; menu.inverted ¬ NIL; }; newEntry#menu.inverted => { IF menu.inverted#NIL THEN InvertEntry[menu, parent]; menu.inverted ¬ newEntry; menu.lineInverted ¬ row; InvertEntry[menu, parent]; }; ENDCASE; }; }; HitMenu: PUBLIC PROC [menu: Menu, parent: ViewerClasses.Viewer, mousePos: TIPUser.TIPScreenCoords, button: MouseButton, shift, control: BOOL ¬ FALSE] = { delta: INTEGER ~ (menu.y-mousePos.mouseY); IF delta >= 0 THEN { row: NAT ~ NAT[delta]/ViewerSpecs.menuHeight; entry: MenuEntry ¬ ResolveEntry[menu, row, mousePos.mouseX]; 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, button, shift, control]; }; }; ProcessMenuHit: ENTRY PROC [entry: MenuEntry, menu: Menu, parent: ViewerClasses.Viewer, button: MouseButton, shift, control: BOOL] = TRUSTED { SELECT entry.guardState FROM guarded => { entry.guardState ¬ arming; Process.Detach[FORK ArmMenuProc[entry, menu, parent]]; IF entry.documentation#NIL THEN Process.Detach[ FORK Document[entry.documentation, parent, entry.clientData, button, shift, control]]; }; arming=> NULL; -- no action armed => { IF entry.guarded THEN entry.guardState ¬ guarded; IF entry.fork THEN Process.Detach[FORK MenuPusher[entry, menu, parent, button, shift, control]] ELSE MenuPusher[entry, menu, parent, button, shift, control, FALSE]; }; ENDCASE; }; ArmMenuProc: ENTRY PROC [entry: MenuEntry, menu: Menu, parent: ViewerClasses.Viewer] = { menuWaitCondition: CONDITION; <> TRUSTED {Process.SetTimeout[@menuWaitCondition, Process.MsecToTicks[armingTime]]}; WAIT menuWaitCondition; IF entry.guardState = arming THEN { entry.guardState ¬ armed; RedrawMenu[parent, menu, entry]; TRUSTED {Process.SetTimeout[@menuWaitCondition, Process.MsecToTicks[armedTime]]}; WAIT menuWaitCondition; }; IF entry.guardState#guarded THEN { entry.guardState ¬ guarded; RedrawMenu[parent, menu, entry]; }; }; MenuPusher: PROC [entry: MenuEntry, menu: Menu, parent: ViewerClasses.Viewer, button: MouseButton, shift, control: BOOL ¬ FALSE, normalPriority: BOOL ¬ TRUE] = { entry.greyed ¬ TRUE; RedrawMenu[parent, menu, entry]; IF normalPriority THEN CedarProcess.SetPriority[normal]; entry.proc[parent, entry.clientData, button, shift, control ! ABORTED => CONTINUE]; entry.greyed ¬ FALSE; RedrawMenu[parent, menu, entry]; WindowManager.RestoreCursor[]; }; RedrawMenu: PROC [viewer: Viewer, menu: Menu, entry: MenuEntry] = { IF menu=ViewerPrivate.windowMenu THEN ViewerPrivate.DrawCaptionMenu[viewer, FALSE] ELSE ViewerOps.PaintViewer[viewer, menu, entry=NIL, entry]; }; Document: PUBLIC PROC [info: REF ANY, parent: Viewer, clientData: REF ANY ¬ NIL, mouseButton: MouseButton ¬ red, shift, control: BOOL ¬ FALSE] = { WITH info SELECT FROM doc: REF TEXT => MessageWindow.Append[Rope.FromRefText[doc], TRUE]; doc: REF ClickProc => doc­[parent, clientData, mouseButton, shift, control]; doc: Rope.ROPE => MessageWindow.Append[doc, TRUE]; ENDCASE => ERROR; -- not valid documentation }; ClearMenu: PUBLIC PROC [menu: Menu, parent: ViewerClasses.Viewer, paint: BOOL ¬ TRUE] = { IF menu#NIL AND menu.inverted#NIL THEN { IF paint THEN InvertEntry[menu, parent]; menu.inverted ¬ NIL; }; }; greyGuard: PUBLIC BOOL ¬ FALSE; -- grey text of guarded items else strikeout myGrey: Imager.Color ~ ImagerBackdoor.MakeStipple[001010B]; DrawMenu: PUBLIC PROC [menu: Menu, context: Imager.Context, x, y: INTEGER, whatChanged: REF ANY ¬ NIL] = { quitEarly: BOOL ¬ FALSE; ey: INTEGER ¬ y; menu.x ¬ x; menu.y ¬ y; Imager.SetFont[context, menuFont]; FOR row: NAT IN [0..menu.linesUsed) DO ey ¬ ey-ViewerSpecs.menuHeight; Imager.SetXYI[context, x+ViewerPrivate.menuHLeading, ey+baselineOffset]; FOR e: MenuEntry ¬ menu.lines[row], e.link UNTIL e=NIL DO ex: INTEGER ~ x+e.xPos; IF whatChanged#NIL THEN { IF e=whatChanged THEN { Imager.SetColor[context, Imager.white]; Imager.MaskRectangleI[context, ex-margin, ey, e.width+margin*2, fontHeight]; quitEarly ¬ TRUE; } ELSE { Imager.SetXRelI[context, e.width+ViewerPrivate.menuHSpace]; LOOP }; }; IF e.greyed THEN { Imager.SetColor[context, myGrey]; Imager.MaskRectangleI[context, ex-margin, ey, e.width+margin*2, fontHeight]; }; Imager.SetColor[context, Imager.black]; Imager.ShowRope[context, e.name]; IF e.guarded AND e.guardState#armed THEN { Imager.MaskRectangleI[context, ex-1, ey+baselineOffset+3, e.width+2, 1]; }; IF quitEarly THEN RETURN; Imager.SetXRelI[context, ViewerPrivate.menuHSpace]; ENDLOOP; ENDLOOP; menu.inverted ¬ NIL; }; ResolveEntry: PROC [menu: Menu, line, x: INTEGER] RETURNS [entry: MenuEntry] = { IF line NOT IN [0..menu.linesUsed) THEN RETURN[NIL]; FOR e: MenuEntry ¬ menu.lines[line], e.link UNTIL e=NIL OR e.xPos>x DO IF x IN [e.xPos..e.xPos+e.width) THEN RETURN[e]; ENDLOOP; RETURN[NIL]; }; InvertEntry: PROC [menu: Menu, parent: Viewer] = { IF ~parent.destroyed AND ~parent.iconic THEN { menuHeight: INTEGER ~ ViewerSpecs.menuHeight; x: INTEGER ~ menu.inverted.xPos-margin; w: INTEGER ~ menu.inverted.width+(2*margin); y: INTEGER ~ menu.y-menu.lineInverted*menuHeight; ViewerPrivate.InvertForMenus[parent, x, y, w, -menuHeight]; }; }; ComputeFontInfo: PROC = { extents: ImagerFont.Extents ~ ImagerFont.FontBoundingBox[menuFont]; fontHeight ¬ Real.Round[extents.descent+extents.ascent]; baselineOffset ¬ Real.Round[extents.descent]; }; <> <<>> ComputeFontInfo[]; END.