DIRECTORY Basics USING[LongMult, LongDiv], Booting USING [ CheckpointProc, RollbackProc, RegisterProcs ], ClassIncreek USING [ Action, ActionBody ], ClassInscript USING [ Inscript, SetWritePage, WriteEntry ], DisplayFace USING [ refreshRate ], Keys USING [ KeyBits ], Interminal, InterminalExtra USING [], --exports only InterminalExtras USING [], --exports only Intime USING [ AdjustEventTime, DeltaDeltaTime, DeltaTime, EventTime, EventTimeDifference, maxDeltaDeltaTime, maxDeltaTime, msPerDeltaTick, MsTicks, MsTicksToDeltaTime, ReadEventTime, SubtractMsTicksFromEventTime ], Loader USING [ MakeProcedureResident, MakeGlobalFrameResident ], PrincOpsUtils USING[COPY], Process USING [ Priority, GetPriority, SetPriority ], Terminal USING [ Create, GetBWCursorPattern, GetBWCursorPosition, GetColorCursorPattern, GetKeys, GetMousePosition, Position, RegisterNotifier, SetBWCursorPattern, SetBWCursorPosition, SetColorCursorPattern, SetColorCursorPosition, SetColorCursorState, SetMousePosition, SwapAction, SwapNotifier, Virtual, WaitForBWVerticalRetrace], UserProfile USING [ProfileChangedProc, Number, CallWhenProfileChanges]; InterminalImpl: MONITOR IMPORTS Basics, Booting, ClassInscript, DisplayFace, Intime, Loader, PrincOpsUtils, Process, Terminal, UserProfile EXPORTS Interminal, InterminalExtra, InterminalExtras SHARES Interminal = { OPEN Interminal, ClassIncreek, ClassInscript, Intime; inscript: ClassInscript.Inscript _ NIL; terminal: PUBLIC Terminal.Virtual _ Terminal.Create[]; wordlength: CARDINAL = 16; Internal: ERROR[code: INTEGER] = CODE; -- "can't happen errors" mousePosition: Terminal.Position _ terminal.GetMousePosition[]; keyState: KeyState; -- _currentKeys^, see init -- eventTime: EventTime; -- _ReadEventTime[], initialized below lastMousePosition: Terminal.Position; lastKeyState: KeyState; -- _keyState, to start, see init lastEventTime: EventTime; allUp: PUBLIC KeyState _ [words[words: [177777B, 177777B, 177777B, 177777B, 177777B]]]; doTrack: BOOL_TRUE; hotX: INTEGER_0; hotY: INTEGER_0; vBWEscape: INTEGER _ 10; -- escape velocity from BW display vColorEscape: INTEGER _ 10; -- escape velocity from color display allZeros: CursorArray _ ALL[0]; grainDots: INTEGER; defaultGrainDots: INTEGER_5; grainMsTicks: Intime.MsTicks; defaultGrainTicks: Intime.MsTicks_200; -- 5 actions per second grainTicks: INTEGER; grainTicksLeft: INTEGER; rR: INTEGER=DisplayFace.refreshRate*2; grainMsPerTick: Intime.MsTicks _ (1000+rR-1)/rR; --System pulse duration in Ms. SetCursorOffset: PUBLIC PROC[deltaX, deltaY: INTEGER, enableTracking: BOOL_TRUE] ={ hotX_deltaX; hotY_deltaY; doTrack_enableTracking; }; GetCursorOffset: PUBLIC PROC RETURNS[deltaX, deltaY: INTEGER, trackingEnabled: BOOL] = { RETURN[deltaX: hotX, deltaY: hotY, trackingEnabled: doTrack]; } ; SetCursorPosition: PUBLIC PROC[posX, posY: INTEGER, enableTracking: BOOL_TRUE] = { doTrack_enableTracking; terminal.SetBWCursorPosition[[x: posX, y: posY]] }; GetCursorPosition: PUBLIC PROC RETURNS[deltaX, deltaY: INTEGER, trackingEnabled: BOOL] = { pos: Terminal.Position = terminal.GetBWCursorPosition[]; RETURN[deltaX: pos.x, deltaY: pos.y, trackingEnabled: doTrack]; }; hasColor: BOOL _ FALSE; DisplayRec: TYPE = RECORD[xMin,xMax,yMin,yMax: INTEGER, color: BOOL]; left,right,display: POINTER TO DisplayRec; leftRep: DisplayRec; rightRep: DisplayRec; ActionRecorder: PROC = { -- Runs as detached process, records terminal actions -- DO WaitForTick[]; -- run once per tick IF hasColor THEN HardCase[] ELSE { mousePosition _ terminal.GetMousePosition[]; mousePosition.x _ MAX[MIN[display.xMax, mousePosition.x], display.xMin]; mousePosition.y _ MAX[MIN[display.yMax, mousePosition.y], display.yMin]; terminal.SetMousePosition[mousePosition]; IF doTrack THEN terminal.SetBWCursorPosition[[x: mousePosition.x+hotX, y: mousePosition.y+hotY]]; }; keyState _ GetTerminalKeys[]; IF keyState # lastKeyState THEN { eventTime _ ReadEventTime[]; []_EnterKeyState[] } ELSE DoMousePosition[]; ENDLOOP; }; HardCase: PROC = INLINE { pattern: CursorArray; updateCursorBits: BOOL _ FALSE; mouse: Terminal.Position _ terminal.GetMousePosition[]; toH,fromH: INTEGER; hideCursor: PROC = INLINE { IF display.color THEN { pattern _ terminal.GetColorCursorPattern[]; [] _ terminal.SetColorCursorPosition[[-100,-100]]; } ELSE { pattern _ terminal.GetBWCursorPattern[]; terminal.SetBWCursorPattern[allZeros]; terminal.SetBWCursorPosition[[-100,-100]] --hides bw cursor }; }; setCursorBits: PROC = INLINE { IF display.color THEN terminal.SetColorCursorPattern[pattern] ELSE terminal.SetBWCursorPattern[pattern]; }; mouse.y _ MAX[MIN[mouse.y,display.yMax], display.yMin]; SELECT TRUE FROM mouse.x>display.xMax => { -- the mouse is moving right IF display=left AND (mouse.x-display.xMax>(IF display.color THEN vColorEscape ELSE vBWEscape)) THEN { -- move to right display hideCursor[]; fromH _ display.yMax; display _ right; toH _ display.yMax; mousePosition.x _ display.xMin; terminal.SetMousePosition[mousePosition]; mouse.y _ Basics.LongDiv[Basics.LongMult[mouse.y,toH],fromH]; setCursorBits[]} ELSE mousePosition.x _ display.xMax }; mouse.x { -- the mouse is moving left IF display=right AND (display.xMin-mouse.x>(IF display.color THEN vColorEscape ELSE vBWEscape)) THEN { -- move to left display hideCursor[]; fromH _ display.yMax; display _ left; toH _ display.yMax; mousePosition.x _ display.xMax; terminal.SetMousePosition[mousePosition]; mouse.y _ Basics.LongDiv[Basics.LongMult[mouse.y,toH],fromH]; setCursorBits[]} ELSE mousePosition.x _ display.xMin }; ENDCASE => mousePosition.x _ mouse.x; mousePosition.y _ mouse.y; terminal.SetMousePosition[mousePosition]; IF doTrack THEN { cursorPosition: Terminal.Position ~ [x: mousePosition.x+hotX, y: mousePosition.y+hotY]; IF ~display.color THEN terminal.SetBWCursorPosition[cursorPosition] ELSE terminal.SetColorCursorPosition[cursorPosition]; }; }; -- end HardCase -- Mouse position and keyboard state recording functions -- DoMousePosition: PROC = INLINE { -- check threshholds IF (grainTicksLeft_grainTicksLeft-1)>0 THEN RETURN; grainTicksLeft_grainTicks; IF mousePosition#lastMousePosition THEN { eventTime _ ReadEventTime[]; []_EnterMousePosition[FALSE] }; }; mouseAB: mousePosition ActionBody _ [contents: mousePosition[mousePosition: [mousePosition.x, FALSE, mousePosition.y]]]; maxDeltaMouse: INTEGER = LAST[[-8..8)]; EnterMousePosition: PROC[enterFullPos: BOOL] RETURNS [enteredFull: BOOL_FALSE] = { dX: INTEGER = mousePosition.x-lastMousePosition.x; dY: INTEGER = mousePosition.y-lastMousePosition.y; mouseA: Action; IF NOT enterFullPos AND (ABS[dX]<=maxDeltaMouse AND ABS[dY]<=maxDeltaMouse) THEN { dMouseB: deltaMouse ActionBody _ [contents: deltaMouse[value: [dX, -dY]]]; --flip the origin mouseA_@dMouseB; } ELSE { y: INTEGER _ IF display.color THEN terminal.colorHeight-mousePosition.y-1 ELSE terminal.bwHeight-mousePosition.y-1; mouseAB.mousePosition_[mousePosition.x, display.color, y]; mouseA_@mouseAB; }; IF EnterAction[mouseA].enteredFull THEN RETURN[TRUE]; lastMousePosition _ mousePosition; }; KMethod: TYPE = {action, state}; EnterKeyState: PROC [method: KMethod _ action] RETURNS [enteredFull: BOOL_FALSE] = { Kn: PROCEDURE [v: CARDINAL] RETURNS [KeyName] = INLINE {RETURN[LOOPHOLE[v]]}; Kv: PROCEDURE [n: KeyName] RETURNS [CARDINAL] = INLINE {RETURN[LOOPHOLE[n]]}; IF mousePosition#lastMousePosition AND EnterMousePosition[FALSE].enteredFull THEN RETURN[TRUE]; FOR i: CARDINAL IN [0..SIZE[KeyState]) DO IF keyState.words[i] # lastKeyState.words[i] THEN FOR j: KeyName IN [Kn[i*wordlength]..Kn[(i + 1)*wordlength]) DO x: updown = keyState.bits[j]; IF x = lastKeyState.bits[j] THEN LOOP; -- bit difference detected -- {aUpDown: keyUp ActionBody _ IF x = up THEN [contents: keyUp[value: j]] ELSE IF method = action THEN [contents: keyDown[value: j]] ELSE [contents: keyStillDown[value: j]]; IF EnterAction[@aUpDown].enteredFull THEN RETURN[TRUE]; }; ENDLOOP; ENDLOOP; lastKeyState _ keyState; }; EnterFullKeyState: PROCEDURE RETURNS [enteredFull: BOOL] = { aAllUp: keyStillDown ActionBody _ [contents: keyStillDown[value: allUp]]; IF EnterKeyState[].enteredFull THEN RETURN[TRUE]; -- enter any new actions -- lastKeyState _ allUp; IF EnterAction[@aAllUp].enteredFull THEN RETURN[TRUE];--reader should clear state next time RETURN[EnterKeyState[state].enteredFull]; }; -- enter current state of all down keys -- EnterTime: PROCEDURE RETURNS [dTime: DeltaDeltaTime, enteredFull: BOOL] = INLINE { a: deltaEventTime ActionBody; difference: MsTicks _ EventTimeDifference[@eventTime, @lastEventTime]; deltaTime: CARDINAL; -- s/b DeltaTime, but can be out of range [deltaTime, difference] _ MsTicksToDeltaTime[difference]; IF deltaTime > maxDeltaTime THEN RETURN[0, EnterFullTime[].enteredFull]; SubtractMsTicksFromEventTime[@eventTime, difference]; IF deltaTime <= maxDeltaDeltaTime THEN RETURN[deltaTime, FALSE]; a _ [contents: deltaEventTime[value: deltaTime]]; RETURN[0, EnterAction[@a].enteredFull]; }; -- Unconditionally enter the current absolute time. -- EnterFullTime: PROCEDURE RETURNS [enteredFull: BOOL] = { a: eventTime ActionBody _ [contents: eventTime[eventTime: eventTime]]; RETURN[EnterAction[@a].enteredFull]; }; EnterFullState: PROCEDURE = INLINE { IF ~EnterFullTime[].enteredFull AND ~ EnterFullKeyState[].enteredFull THEN []_EnterMousePosition[TRUE]; }; InsertAction: PUBLIC PROCEDURE [action: ClassIncreek.ActionBody] = { [] _ EnterAction[@action]; }; EnterAction: PROCEDURE [action: Action] RETURNS [enteredFull: BOOL_FALSE] = { length: CARDINAL = -- have to enumerate the tag type here -- WITH thisA: action SELECT FROM deltaEventTime, keyUp, keyDown, keyStillDown => SIZE[keyUp ActionBody], eventTime => SIZE[eventTime ActionBody], mousePosition => SIZE[mousePosition ActionBody], penPosition => SIZE[penPosition ActionBody], deltaMouse => SIZE[deltaMouse ActionBody], ENDCASE => ERROR Internal[1]; action.deltaDeltaTime _ 0; WITH action SELECT FROM eventTime, deltaEventTime => NULL; ENDCASE => { [action.deltaDeltaTime, enteredFull] _ EnterTime[]; IF enteredFull THEN RETURN[TRUE]; --full action record was made }; IF ~WriteEntry[inscript, DESCRIPTOR[action, length]] THEN { -- page full, start a new one with complete information -- IF ~SetWritePage[inscript] THEN RETURN[FALSE]; -- action lost because inscript could not be expanded EnterFullState[]; RETURN[TRUE]; }; lastEventTime _ eventTime; };-- time only changes when something's been entered SetMousePosition: PUBLIC PROC[pos: MousePosition] = { terminal.SetMousePosition[[x: pos.mouseX, y: pos.mouseY]]; }; GetMousePosition: PUBLIC PROC RETURNS [pos: MousePosition] = { p: Terminal.Position = terminal.GetMousePosition[]; pos_[p.x, display.color, p.y];}; DefaultMouseGrain: PUBLIC PROC RETURNS [ticks: Intime.MsTicks, dots: INTEGER] = { RETURN[defaultGrainTicks, defaultGrainDots]; }; SetMouseGrain: PUBLIC PROC[ticks: Intime.MsTicks, dots: INTEGER] = { grainDots_MIN[defaultGrainDots, dots]; grainMsTicks_MIN[defaultGrainTicks, ticks]; grainTicks_(grainMsTicks+grainMsPerTick-1)/grainMsPerTick; grainTicksLeft_0; }; SetCursorPattern: PUBLIC PROC [cursorPattern: CursorArray] = { IF display.color THEN terminal.SetColorCursorPattern[cursorPattern] ELSE terminal.SetBWCursorPattern[cursorPattern]; }; GetCursorPattern: PUBLIC PROC RETURNS [cursorPattern: CursorArray] = { IF display.color THEN RETURN[terminal.GetColorCursorPattern[]] ELSE RETURN[terminal.GetBWCursorPattern[]]; }; TurnOnColorCursor: PUBLIC PROC[nbits: NAT, onLeft: BOOL _ TRUE] = { color: POINTER TO DisplayRec; IF hasColor THEN TurnOffColorCursor[]; IF NOT terminal.hasColorDisplay THEN RETURN; IF onLeft THEN {display _ right; color _ left} ELSE {display _ left; color _ right}; color^ _ [xMin: 0, xMax: terminal.colorWidth-1, yMin: 0, yMax: terminal.colorHeight-1, color: TRUE]; hasColor _ TRUE; terminal.SetColorCursorPosition[[-100, -100]]; [] _ terminal.SetColorCursorState[$visible]; }; TurnOffColorCursor: PUBLIC PROC = { IF hasColor THEN { IF display.color THEN { display _ (IF display=left THEN right ELSE left); [] _ terminal.SetColorCursorState[$invisible]; terminal.SetBWCursorPattern[terminal.GetColorCursorPattern[]]; }; hasColor _ FALSE; IF display=left THEN right^ _ left^ ELSE left^ _ right^; }; }; HasPen: PUBLIC PROC [display: HasPenType] = {}; WaitForTick: PROC = INLINE { WaitForEnabled[]; terminal.WaitForBWVerticalRetrace[]}; WaitForEnabled: ENTRY PROC = INLINE { UNTIL terminalHandlerEnabled DO WAIT enableTerminalHandler; ENDLOOP}; -- Initialization -- terminalProcess: PROCESS; StartActionRecorder: PUBLIC PROC [scr: ClassInscript.Inscript] = { -- <> -- KeyboardPriority: Process.Priority = 6; save: Process.Priority = Process.GetPriority[]; inscript _ scr; right _ @rightRep; left _ @leftRep; right^ _ left ^ _ [xMin: 0, xMax: terminal.bwWidth-1, yMin: 0, yMax: terminal.bwHeight-1, color: FALSE]; display _ right; --most bw displays are to the right of the color monitor Process.SetPriority[KeyboardPriority]; terminalProcess _ FORK ActionRecorder[]; Process.SetPriority[save]; Terminal.RegisterNotifier[terminal, InscriptController]; Booting.RegisterProcs[c: InscriptCPProc, r: InscriptRBProc]; SetMouseGrain[defaultGrainTicks, defaultGrainDots]; EnableTerminalHandler[]; -- InscriptController[enable]; We're it. }; InscriptController: Terminal.SwapNotifier = TRUSTED { SELECT action FROM coming => { IF terminalHandlerCurrent = 0 THEN EnableTerminalHandler[]; terminalHandlerCurrent _ terminalHandlerCurrent + 1; }; going => { terminalHandlerCurrent _ terminalHandlerCurrent - 1; IF terminalHandlerCurrent = 0 THEN DisableTerminalHandler[]; }; here, gone => NULL; ENDCASE=>ERROR; }; InscriptCPProc: Booting.CheckpointProc = TRUSTED{ DisableTerminalHandler[]; }; InscriptRBProc: Booting.RollbackProc = TRUSTED{ EnableTerminalHandler[]; }; terminalHandlerCurrent: INTEGER _ 0; terminalHandlerEnabled: BOOL _ FALSE; enableTerminalHandler: CONDITION; EnableTerminalHandler: ENTRY PROC = {ENABLE UNWIND => NULL; -- set things up -- Intime.AdjustEventTime[TRUE]; eventTime _ lastEventTime _ ReadEventTime[]; keyState _ GetTerminalKeys[]; lastKeyState _ keyState; allUp.keyNames.blank _ keyState.keyNames.blank; EnterFullState[]; terminalHandlerEnabled_TRUE; NOTIFY enableTerminalHandler; }; GetTerminalKeys: PROC RETURNS[keyState: KeyState] = INLINE { ks: Keys.KeyBits; ks _ terminal.GetKeys[]; --currentKeys^; add pen here PrincOpsUtils.COPY[to: @keyState, from: @ks, nwords: SIZE[KeyState]]; }; DisableTerminalHandler: ENTRY PROC = {terminalHandlerEnabled_FALSE}; SwitchSideOfColorDisplay: PUBLIC SAFE PROC ~ CHECKED { -- InterminalExtras temp: POINTER TO DisplayRec ~ right; right _ left; left _ temp; }; ColorDisplayOnLeft: PUBLIC SAFE PROC RETURNS[BOOL] ~ TRUSTED { -- InterminalExtras RETURN[left.color]; }; ProcessProfile: UserProfile.ProfileChangedProc ~ CHECKED { vBWEscape _ UserProfile.Number["Interminal.VBWEscape", 0]; vColorEscape _ UserProfile.Number["Interminal.VColorEscape", 0]; }; Loader.MakeProcedureResident[--e.g.--ActionRecorder]; Loader.MakeGlobalFrameResident[--e.g.--ActionRecorder]; UserProfile.CallWhenProfileChanges[ProcessProfile]; }. œInterminalImpl.mesa Copyright (C) 1982, 1983, 1985 by Xerox Corporation. All rights reserved. Andrew Birrell, August 23, 1983 9:49 am keyboard handler performance Stone, June 23, 1982 2:21 pm color cursor and pen McGregor, 16-Apr-82 9:26:01 Flushed delta x&y mouse ops, Swinehart, April 29, 1982 5:16 pm, register with multiplexor, etc. Paul Rovner, August 15, 1983 10:01 am Russ Atkinson, September 23, 1983 3:30 pm Doug Wyatt, January 23, 1985 3:15:07 pm PST added Sindhu's UserProfile options Interminal.MousePosition now has a boolean on it to declare which display the point is on to keep things consistent with the hardware world, we do all our internal calculations with InternalMP, then construct a MousePosition when recording the Action Values at time t, maintained correct by ActionRecorder main loop Values at time t-1, also maintained by main loop Mouse grain threshhold stuff This procedure runs as an independent process. It constitutes the low-level action recording mechanisms for user input actions. moves the mouse between displays check if mouse is moving out of current display an integer divide, but we get our precision where we need it an integer divide, but we get our precision where we need it grainDots test removed 4-15-82 DCS in this procedure we convert the mousePosition, which corresponds to the cursor's coordinates to a value which is relative to the lower left corner of the display recording kbd action or recording current downness Hi-res mouse recording Unconditionally enter the current time, preferably as an increment to the previous entry. Assume that an absolute time value is entered occasionally. adjust time for roundoff in delta a procedure to fake actions currently exported to InterminalExtra. Should be exported to Interminal may insert single action, or may record the full state of the input devices. enteredFull tells which. Would be nice if something in the language would handle this. It's not only convenience and efficiency; the code should not used to change hardware's idea of where the mouse is; use with great care!! this initializes the Color display. If onLeft is false, it is assumed that the display is on the right. set current display to bw make both recs bw this declares on which display the pen is located Operating System Interface Procedures initialize the display records to reflect the standard display [vt: Virtual, action: SwapAction, clientData: REF ANY] START HERE Ê„˜Jšœ™šœJ™JJšœE™EJšœ2™2Jšœ:™:JšœC™CJšœ%™%J™)JšœO™O—J˜šÏk ˜ Jšœœ˜ Jšœœ1˜>Jšœ œ˜*Jšœœ(˜;Jšœ œ˜"Jšœœ ˜J˜ JšœœÏc˜(Jšœœž˜)šœœ˜J˜ÅJ˜—Jšœœ4˜@Jšœœœ˜Jšœœ(˜5šœ œ˜Jšœ»˜»—Jšœ œ6˜G˜J˜——Jšœ˜Jšœk˜rJšœ.˜5Jšœ ˜Jšœ˜šœ2˜6J˜Jšœ#œ˜'Jšœ œ&˜6J˜—Jšœú™úJšœ œ˜J˜Jšœ œœœž˜?Jšœ@™@J˜?Jšœž˜1Jšœž&˜J˜—Jšœ œœ˜Jšœœ˜Jšœœ˜J˜Jšœ œ ž˜;Jšœœ ž"˜A˜Jšœœ˜J˜—šœ™šœ œ˜Jšœœ˜—˜Jšœ'ž˜>—šœ œ˜šœœ˜Jšœœ˜'—Jšœ1ž˜O—J˜—š Ïnœœœœœœ˜TJ˜1J˜—š Ÿœœœœœœ˜XJšœ7˜=Jšœ˜J˜—š Ÿœœœ œœœ˜SJ˜Jšœ0˜0J˜—š Ÿœœœœœœ˜ZJšœ8˜8Jšœ9˜?Jšœ˜J˜—Jšœ œœ˜Jš œ œœœ œ˜EJšœœœ ˜*J˜J˜˜Jšœ=žB™J˜—šŸœœž8˜Qš˜Jšœž˜#J˜Jšœ œ ˜šœ˜J˜,Jšœœœ/˜HJšœœœ/˜HJ˜)šœ ˜J˜Q—J˜—J˜Jšœœ4˜SJšœ˜Jšœ˜—šœ˜J˜——šŸœœœ˜Jšœ ™ Jšœ/™/J˜Jšœœœ˜J˜7Jšœ œ˜šœ œœ˜šœ˜šœ˜Jšœ+˜+Jšœ2˜2J˜—šœ˜J˜(J˜&Jšœ*ž˜;Jšœ˜—J˜——J˜šœœœ˜Jšœœ(˜=Jšœ&˜*J˜J˜—Jšœ œœ&˜7šœœ˜šœž˜6š œœœœœ œž˜~J˜ J˜J˜J˜J˜J˜)———šœ<™J˜Jšœ#˜'šœž˜5š œœœœœ œž˜~J˜ J˜J˜J˜J˜J˜)———šœ<™—J˜J˜—˜#Jšœ:œ˜T—Jšœœœ ˜'J˜š Ÿœœœœœœ˜RJšœ¢™¢Jšœœ'˜2Jšœœ'˜2J˜šœœœœœœœ˜RJšœKž˜\J˜—šœ˜šœœœ˜Jšœ(˜,Jšœ%˜)—J˜:J˜—Jšœ!œœœ˜5J˜"J˜J˜J˜—Jšœ œ˜ J˜šŸ œœ˜.Jšœœœ˜%Jšœ2™2JšŸœ œœœ œœœ˜MJšŸœ œœœœœœ˜MJšœ™šœ!œœ ˜LJšœœœ˜—š œœœœ ˜)šœ+˜1šœ œ,˜?J˜Jšœœœž˜D˜Jšœœ˜*šœœœ˜:Jšœ$˜(——Jš œ#œœœœœ˜L———J˜J˜—šŸœ œœœ˜J˜9Jšœœœ!˜HJ˜5Jšœ!™!Jšœ œœ œ˜@J˜1Jšœ!˜'Jšœ˜J˜—Jšž6˜6J˜šŸ œ œœœ˜8J˜FJšœ˜$Jšœ˜J˜—šŸœ œœ˜$šœ˜#Jšœ"œœ˜C—šœ˜J˜——šŸ œœ œ&˜DJšœ™JšœH™HJ˜J˜J˜—Jš Ÿ œ œœœœ˜MJšœL™Lšœ™šœœ˜Jšœ=™=Jšœ=™=Jšž)˜)šœœ˜˜/Jšœ˜—Jšœ œ˜(Jšœœ˜0Jšœœ˜,Jšœœ˜*Jšœœ ˜——J˜šœœ˜Jšœœ˜"šœ˜ J˜3Jš œ œœœž˜@J˜——šœ œœ˜;Jšž:˜:Jš œœœœž6˜eJ˜Jšœœ˜—Jšœž2˜OJ˜J˜—JšœK™KšŸœœœ˜5J˜=—šŸœœœœ˜>J˜3J˜ J˜—š Ÿœœœœœ˜QJšœ)˜/J˜—šŸ œœœœ˜DJšœ œ˜&Jšœ œ˜+J˜:J˜J˜—šŸœœœ!˜>Jšœœ.˜CJšœ,˜0J˜J˜—šŸœœœœ!˜FJšœœœ"˜>Jšœœ ˜+J˜J˜—JšœK™KJšœ™š Ÿœœœœ œœ˜CJšœœœ ˜Jšœ œ˜&Jšœœœœ˜,Jšœœ ˜.Jšœ!˜%šœV˜VJšœœ˜ —Jšœ œ˜J˜.J˜,Jšœ˜—J˜šŸœœœ˜#šœ œ˜Jšœ™šœœ˜Jšœ œœœ˜1Jšœ.˜.Jšœ>˜>Jšœ˜—Jšœ œ˜Jšœœœ˜8Jšœ™Jšœ˜—Jšœ˜—J˜Jšœ1™1JšŸœœœ˜/J˜Jšœ%™%J˜šŸ œœœ˜J˜J˜%J˜—šŸœœœœ˜%šœ˜Jšœ˜Jšœ˜ J˜——Jšž˜J˜Jšœœ˜J˜šŸœœœ"˜BJšžO˜OJšœ'˜'Jšœ/˜/Jšœ>™>J˜J˜#˜5Jšœ+œ˜2—Jšœž8˜IJšœ&˜&Jšœœ˜(Jšœ˜J˜8J˜