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 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; }; ComputeFontInfo[]; UserProfile.CallWhenProfileChanges[ReEstablishUserProfileParameters]; END. 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, November 14, 1983 1:49 pm assert: state=arming Note: the menu parameter might be the windowManager caption menu rather than v.menu. ResolveEntry 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 cached entry, the line that contains the entry, and the menu that contains the line. 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 cache fails, we test everybody. ReRegisterMenu: PUBLIC PROC [menu: Menu, paint: BOOL _ TRUE] = { newMenu: ViewerMenuRec; MenuDefinitonChanged: ViewerOps.EnumProc = { -- [v: Viewer] RETURNS [BOOL _ TRUE] -- menus: MenuInfo = NARROW[v.menus]; FOR m: LIST OF LineInfo _ menus.list, m.rest UNTIL m = NIL DO IF 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 m.first.name=menu.name THEN { m.first _ menu; newMenu _ MakeNewViewerMenuRec[m.first]; ViewerOps.EnumerateViewers[MenuDefinitonChanged]; RETURN; }; ENDLOOP; ERROR targetNotFound; }; GetRegisteredMenu: PUBLIC PROC[name: ATOM] RETURNS [Menu] = { FOR m: LIST OF Menu _ registeredMenus, m.rest UNTIL m = NIL DO menu: Menu = m.first; IF 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]; }; Addline: PUBLIC PROC[viewer: Viewer, name: ATOM, paint: BOOL _ TRUE, addBefore: ATOM _ NIL] = { menus: MenuInfo = 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 LineInfo _ NIL; foundIt: BOOLEAN _ FALSE; FOR m: LIST OF LineInfo _ menus.list, m.rest UNTIL m = NIL DO IF 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 LineInfo _ NIL; FOR m: LIST OF LineInfo _ 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 LineInfo _ NIL; FOR m: LIST OF LineInfo _ 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: ATOM, addBefore: ATOM _ NIL, paint: BOOL _ TRUE] = { provided for convenience of BuildWindowMenus in WindowManagerImpl IF WindowManagerPrivate.windowMenu = NIL THEN { IF addBefore # NIL THEN SIGNAL targetNotFound; WindowManagerPrivate.windowMenu _ NEW[ViewerMenusRec]; WindowManagerPrivate.windowMenu.list _ LIST[MakeNewViewerMenu[name]]; } ELSE IF addBefore # NIL THEN { place in the slot before 'addBefore' prev: LIST OF LineInfo _ NIL; foundIt: BOOLEAN _ FALSE; FOR m: LIST OF LineInfo _ WindowManagerPrivate.windowMenu.list, m.rest UNTIL m = NIL DO IF 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 LineInfo _ NIL; FOR m: LIST OF LineInfo _ WindowManagerPrivate.windowMenu.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 LineInfo _ NIL; FOR m: LIST OF LineInfo _ WindowManagerPrivate.windowMenu.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]; }; }; The user-profilable menu options: This routine is called whenever the user profile changes: Note: all coordinates here are relative to the viewer's lower left corner! 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.windowMenu.x _ windowBorderSize; WindowManagerPrivate.windowMenu.y _ v.wh - captionHeight; WindowManagerPrivate.windowMenu.w _ v.ww - (2 * windowBorderSize); WindowManagerPrivate.windowMenu.h _ captionHeight; IF guard THEN { SetDisplay[$windowDestroyMenu, FALSE]; SetDisplay[$windowGuardedDestroyMenu, TRUE]; } ELSE { SetDisplay[$windowDestroyMenu, TRUE]; SetDisplay[$windowGuardedDestroyMenu, FALSE]; }; IF color THEN SetDisplay[$windowColorMenu, TRUE] ELSE SetDisplay[$windowColorMenu, FALSE]; FOR m: LIST OF LineInfo _ WindowManagerPrivate.windowMenu.list, m.rest UNTIL m = NIL DO IF NOT m.first.active THEN LOOP; FOR e: LIST OF EntryInfo _ m.first.entries, e.rest UNTIL e = NIL DO e.first.w _ 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.x _ CurrentX; e.first.y _ CurrentY; hack for special clicking GROW and CLOSE IF Rope.Equal[e.first.commonData.name, "Grow"] THEN { WindowManagerPrivate.growPos.mouseX _ e.first.x+1; WindowManagerPrivate.growPos.color _ FALSE; WindowManagerPrivate.growPos.mouseY _ e.first.y+1; }; IF Rope.Equal[e.first.commonData.name, "Close"] THEN { WindowManagerPrivate.closePos.mouseX _ e.first.x+1; WindowManagerPrivate.closePos.color _ FALSE; WindowManagerPrivate.closePos.mouseY _ e.first.y+1; }; CurrentX _ CurrentX + e.first.w + menuHSpace; ENDLOOP; ENDLOOP; }; Êÿ– "cedar" style˜JšÏc&™&Jš-™-J™-J™0J™0J˜šÏk ˜ Jšœžœk˜wJ˜J˜ Jšœžœ ˜JšœžœN˜[Jšœžœ ˜Jšœžœžœ˜Jšœžœ˜ Jšœ žœ?˜PJšœžœžœ%˜DJšœ žœ˜J˜Jšœ žœ9˜HJšœ žœX˜iJšœžœ˜$Jšœžœ5˜OJ˜—Jš œ žœžœžœžœ˜;Jšžœu˜|Jšžœ˜Jšžœ ˜Jšœžœžœ˜!J˜Jšœžœ˜$Jšžœžœžœ˜J˜Jšœžœ˜+Jšœ žœ˜Jšœžœ˜˜J˜—š Ïnœžœžœ$žœžœ˜HJ˜J˜J˜—J˜šŸ œžœžœžœ˜;šžœžœG˜QJšœžœžœ žœ ˜K—J˜J˜—šŸœžœ žœ˜0šŸ œžœžœžœžœžœžœ˜EJšžœžœžœžœžœžœžœ1˜VJšœ˜—šŸœžœ žœ˜0šžœžœ ˜*Jšœ;˜;—Jšœ˜—šŸ œžœžœžœžœžœžœ˜AJšžœžœžœžœžœžœžœ.˜SJšœ˜—Jšžœžœ*˜4J˜J˜—J˜Jšœ žœ˜J˜šŸœžœžœ ˜5Jšœ&˜&Jšœ0˜0šžœžœ˜Jšžœžœžœ˜4Jšœ˜Jšžœžœžœ˜4J˜—J˜J˜—šŸœžœžœ ˜4Jšœ8˜8Jšœ0˜0Jšœžœžœžœ˜8Jšžœžœžœ ˜AJšœžœ˜Jšžœžœ.˜9Jšœ˜J˜—šŸœžœžœ˜,Jšœ6˜6šžœ ž˜šœ ˜ J˜Jšžœžœ%˜AJšžœžœ7˜SJšœ˜—Jšœ žœ ˜šœ ˜ Jšžœžœ˜,Jšœ*žœ˜1Jšœ˜—Jšžœ˜—Jšœ˜J˜—šŸ œžœžœ7˜NJšœž œ˜Jš™JšžœK˜RJšžœ˜šžœžœ˜J˜Jšœ!˜!JšžœJ˜QJšžœ˜Jšœ˜—šžœžœ˜J˜Jšœ!˜!Jšœ˜—Jšœ˜J˜—šŸ œžœžœ$˜>Jšžœ,žœž˜7Jšžœ"žœ˜,Jšœ˜J˜—šŸ œžœD˜TJšœžœžœ˜ Jšœ.˜.Jšœ$˜$Jšžœžœ!˜:Jšžœžœžœ/˜Mšžœ&ž˜,JšœD˜D—Jšžœ.˜2Jšœ$˜$Jšžœžœ!˜:J˜Jšœ˜J˜—šŸ œžœ6˜Fšžœ&ž˜,Jšœ-žœ˜3—JšžœG˜KJšœ˜J˜—š Ÿ œžœžœ(žœžœ˜Nšžœžœžœ˜Jšžœžœ˜(Jšœžœ˜Jšœ˜—Jšœ˜J˜—Jšœ3˜3J˜šŸœžœ;˜PJšœ žœžœ˜Jšœžœ˜!Jšœžœ˜!Jšœžœ ˜šžœ žœ˜J˜'JšœV˜VJ˜—šžœžœ˜J˜!JšœV˜VJ˜—J˜'Jšœ2˜2Jšœ?˜?šžœžœžœžœ ˜?JšœZ˜ZJ˜—J˜J˜—šŸœžœžœ˜0Jšœ&žœžœžœ˜8JšœT™Tšžœ žœž˜Jšœ:žœ˜CJšžœ˜—š žœžœžœ#žœžœž˜GJ˜Jšžœžœ žœžœ˜š žœ žœžœ(žœ žœž˜PJšœ+žœ˜2Jšžœ˜—Jšžœ˜—Jšœžœ˜Jšœ˜—J˜JšœÁ™ÁJ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜J˜šŸ œžœ3˜EJšžœ˜Jšœžœ˜Jšœžœ˜J˜Jšœ ™ Jšžœ˜Jšžœ˜Jšžœžœ)˜1šžœžœ'˜/Jšžœžœ ˜—J˜Jšœ"™"š žœžœžœ#žœžœž˜GJšœ˜Jšžœžœ žœžœ˜š žœ žœžœ(žœ žœž˜PJ˜!š žœžœžœžœžœ˜NJšœ7˜7Jšžœ ˜—Jšžœ˜—Jšžœ˜—Jšžœžœ˜ Jšœ˜—J˜šŸ œžœ%˜6šžœžœžœžœ˜0J˜"Jšœz˜zJ˜—Jšœ˜J˜—šŸœžœ˜Jšœžœ˜.J˜)Jšœ0˜0Jšœ˜J˜—šŸ œžœ%žœ ˜JJšœžœžœ˜%Jšœžœ˜Jšžœžœžœžœžœžœžœ˜)Jš žœžœ žœžœ#žœ˜SJšžœ ˜J˜J˜—š Ÿœžœžœžœžœ™@Jšœ™šÏbœ™,Jšœžœžœ™'Jšœžœ ™"š žœžœžœžœžœž™=Jšžœ#žœ žœ™QJšžœ™—Jšžœžœ™,J™—J™š žœžœžœ žœžœž™>šžœžœ™ Jšœ™Jšœ(™(Jšœ1™1Jšžœ™J™—Jšžœ™—Jšžœ™J™J™—š Ÿœžœžœžœžœ ™=š žœžœžœ žœžœž™>J™Jšžœžœžœ™$Jšžœ™—Jšžœ™J™J™—š Ÿ œžœžœžœžœ™@Jšœžœ™Jšžœžœ$™1J™J™—šŸœžœžœžœ žœžœ žœžœ™_Jšœžœ™'šžœ žœžœ™Jšžœ žœžœžœ™.Jšœžœžœ™KJ™—šžœžœ žœžœ™Jšœ$™$Jšœžœžœ žœ™Jšœ žœžœ™š žœžœžœžœžœž™=šžœžœ™&Jšœ žœ™-Jšœ žœ™J™—J™ Jšžœ™—Jšžœžœ žœžœ™*J™—šžœžœ%žœ™2Jšœ?™?Jšœžœžœ žœ™š žœžœžœžœžœž™=J™ Jšžœ™—Jšœ žœ™*J™—šžœ™JšœX™XJšœžœžœ žœ™šžœžœžœžœžœžœ žœžœ$žœžœ!ž™ŸJ™ Jšžœ™—Jšœ žœ$™4J™—Jšžœžœ$™1J™J™—šŸœžœžœžœ žœžœ žœžœ™ZJšœA™Ašžœ#žœžœ™/Jšžœ žœžœžœ™.Jšœ#žœ™7Jšœ'žœ™EJ™—šžœžœ žœžœ™Jšœ$™$Jšœžœžœ žœ™Jšœ žœžœ™š žœžœžœ9žœžœž™Wšžœžœ™&Jšœ žœ™-Jšœ žœ™J™—J™ Jšžœ™—Jšžœžœ žœžœ™*J™—šžœžœ%žœ™2Jšœ?™?Jšœžœžœ žœ™š žœžœžœ9žœžœž™WJ™ Jšžœ™—Jšœ žœ™*J™—šžœ™JšœX™XJšœžœžœ žœ™šžœžœžœ9žœžœžœ žœžœ$žœžœ!ž™¹J™ Jšžœ™—Jšœ žœ$™4J™—J™J™—šŸ œžœžœžœ˜GJšœžœ˜%Jšžœžœžœžœ˜š žœžœžœ#žœžœž˜GJ˜Jšœ žœ˜šžœžœ˜šžœž˜Jšœžœ˜Jšœžœ˜Jšœžœ ˜(Jšžœ˜—Jšžœžœ ˜=Jšžœ˜J˜—Jšžœ˜—J˜J˜—š Ÿ œžœžœžœžœ žœ˜Ršžœ žœž˜˜š žœžœžœ#žœžœž˜GJ˜Jšžœžœžœžœ˜$Jšžœ˜—J˜—Jš žœžœ žœžœžœ˜)—Jšžœžœ˜J˜J˜—Jšœ!™!Jšœžœžœ:˜PJ•StartOfExpansion)[key: ROPE, default: BOOLEAN _ FALSE]šœ žœ1˜YJšœžœžœ2˜WJ˜Jšœ9™9J˜šŸ œžœ#˜KJ–)[key: ROPE, default: BOOLEAN _ FALSE]šœ<žœ˜BJšœY˜YJšœM˜MJ˜J˜—š Ÿœžœžœ žœžœ˜?Jšžœ ˜Jš œžœžœ žœžœ˜8Jš œžœžœ žœžœžœžœ˜Nšžœžœž˜šœ˜JšœD˜DJšžœ˜J˜—Jšžœžœžœžœžœ žœžœ˜9—J˜J˜—šŸ œžœ#žœ˜BJšžœ ˜JšœJ™JJšœ žœ žœ#˜?Jšœžœ˜š žœžœžœ#žœžœž˜GJ˜Jšœžœ˜!Jšžœžœ žœžœ˜Jšœ˜%š žœžœžœ"žœžœž˜@J˜Jšœžœ ˜*šžœ žœ žœ˜#Jšœ'˜EJšœ˜—J˜&Jšœ˜Jšžœ˜—Jšžœ˜—Jšœ?˜?J˜J˜—š Ÿœžœžœžœ žœ™KJšžœ ™Jšœ@™@Jšœ žœ#™4Jšœ žœ™GJšœ5™5Jšœ9™9JšœB™BJšœ2™2J™šžœžœ™Jšœžœ™&Jšœ&žœ™,J™—šžœ™Jšœžœ™%Jšœ&žœ™-J™—Jšžœžœžœ™0Jšžœžœ™)J™š žœžœžœ9žœžœž™WJšžœžœžœžœ™ š žœžœžœ%žœžœž™Cšœ žœ žœž™;Jšœžœ™ Jšœžœ:™CJšžœžœ™—J™J™Jšœ(™(šžœ-žœ™5Jšœ2™2Jšœ%žœ™+Jšœ2™2J™—šžœ.žœ™6Jšœ3™3Jšœ&žœ™,Jšœ3™3J™—Jšœ-™-Jšžœ™—Jšžœ™—J™J™—J˜JšœE˜EJ˜Jšžœ˜—…—)ÞWñ