DIRECTORY Ascii USING [LF], Basics USING [BITAND], Commander USING [CommandProc, Lookup, Register], CommandExtras USING [MakeUninterpreted], CommandTool USING [ArgumentVector, Failed, Parse], EditedStream USING [SetEcho], FS USING [Error, StreamOpen], IO, IOClasses USING [CreateDribbleOutputStream], List USING [Length, Remove], Loader USING [BCDBuildTime], Menus USING [AppendMenuEntry, CreateEntry, FindEntry, MenuEntry, MenuProc, ReplaceMenuEntry], Process USING [Detach], PupDefs USING [PupAddress, PupPackageMake, PupPackageDestroy], PupStream USING [ConsumeMark, GetPupAddress, PupByteStreamCreate, PupNameTrouble, SecondsToTocks, SendMark, StreamClosing, TimeOut], PupTypes USING [telnetSoc], Rope USING [Cat, Fetch, Find, Length, ROPE, Substr], TiogaOps USING [GetCaret, GetRope, Location], TIPUser USING [InstantiateNewTIPTable, RegisterTIPPredicate, TIPPredicate, TIPTable], TypeScript USING [ChangeLooks, Create, InsertCharAtFrontOfBuffer, TS], UserCredentials USING [Get], ViewerClasses, ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc], ViewerIO USING [CreateViewerStreams], ViewerOps USING [AddProp, DestroyViewer, FetchProp, PaintViewer], ViewerTools USING [GetSelectedViewer, GetSelectionContents, SetSelection]; Chat: CEDAR MONITOR LOCKS h.LOCK USING h: Handle IMPORTS Basics, Commander, CommandExtras, CommandTool, EditedStream, FS, IO, IOClasses, List, Loader, Menus, Process, PupDefs, PupStream, Rope, TiogaOps, TIPUser, TypeScript, UserCredentials, ViewerEvents, ViewerIO, ViewerOps, ViewerTools SHARES Menus, ViewerClasses = BEGIN Handle: TYPE = REF ChatInstanceRecord; State: TYPE = {idle, starting, running, closing, destroy}; DisconnectChar: CHAR = 220C; AbortChar: CHAR = 221C; ConnectChar: CHAR = 222C; LoginChar: CHAR = 223C; RemoteCloseChar: CHAR = 224C; MarkByte: TYPE = [0..256); setLineWidth: MarkByte = 2; setPageLength: MarkByte = 3; timingMark: MarkByte = 5; timingMarkReply: MarkByte = 6; ChatInstanceRecord: TYPE = MONITORED RECORD [ ts: TypeScript.TS, -- the primary typescript state: State _ idle, lorc: CHAR _ 'c, logFileName: Rope.ROPE, logStream: IO.STREAM, keyFile: Rope.ROPE, argv: CommandTool.ArgumentVector, pleaseStop: BOOL _ FALSE, uToSStopped: BOOL _ FALSE, sToUStopped: BOOL _ FALSE, inDestroy: BOOL _ FALSE, serverToUserProcess: PROCESS, userToServerProcess: PROCESS, serverName: Rope.ROPE _ "Ivy", useOldHost: BOOL _ FALSE, keyStream: IO.STREAM, destroyOnClose: BOOL _ FALSE, synchronous: BOOL _ FALSE, setSelection: BOOL _ FALSE, in: IO.STREAM, origOut: IO.STREAM, out: IO.STREAM, tipTable: TIPUser.TIPTable, serverStream: IO.STREAM _ NIL, oldSplit: Menus.MenuEntry _ NIL ]; logFileNumber: INT _ 0; chatInstanceList: LIST OF REF ANY _ NIL; destroyEvent: ViewerEvents.EventRegistration _ NIL; closeEvent: ViewerEvents.EventRegistration _ NIL; chatTipTable: TIPUser.TIPTable; ServerToUser: PROC [h: Handle] = {{ c: CHAR; mySST: MarkByte; DO ENABLE { ABORTED => GOTO Cleanup; PupStream.StreamClosing => { IF NOT h.pleaseStop THEN TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: RemoteCloseChar]; GOTO Cleanup; }; IO.Error => GOTO Cleanup; }; IF h.serverStream.EndOf[] THEN { mySST _ PupStream.ConsumeMark[h.serverStream]; IF mySST = timingMark THEN PupStream.SendMark[h.serverStream, timingMarkReply]; }; c _ h.serverStream.GetChar[! PupStream.TimeOut => { IF h.pleaseStop OR h.state # running THEN GOTO Cleanup ELSE RESUME; }; IO.EndOfStream => { LOOP; }]; IF h.pleaseStop OR h.state # running THEN GOTO Cleanup; IF c # Ascii.LF THEN h.out.PutChar[LOOPHOLE[Basics.BITAND[LOOPHOLE[c, CARDINAL], 177B], CHAR]]; ENDLOOP; EXITS Cleanup => h.sToUStopped _ TRUE; }; }; InitialNegotiations: PROC [h: Handle] = TRUSTED { PupStream.SendMark[h.serverStream, setLineWidth]; h.serverStream.PutChar[0C]; IF h.lorc='l OR h.lorc='x THEN { name, password: Rope.ROPE; [name: name, password: password] _ UserCredentials.Get[]; h.serverStream.PutRope["Login "]; h.serverStream.PutRope[name]; IF h.lorc='l AND Rope.Find[s1: name, s2: "."] = -1 THEN h.serverStream.PutRope[".PA"]; h.serverStream.PutRope[" "]; h.serverStream.PutRope[password]; h.serverStream.PutRope[" \n"]; h.serverStream.Flush[]; }; }; FinishStringWithErrorMsg: PROC [h: Handle, errorMsg: Rope.ROPE] = { IF errorMsg # NIL THEN h.out.PutF[": %s.\n", IO.rope[errorMsg]] ELSE h.out.PutF[".\n"]; }; FromKeys: PROC [h: Handle] = TRUSTED { count: NAT _ 0; { WHILE h.keyStream # NIL AND ~h.keyStream.EndOf[] DO IF h.pleaseStop OR h.state # starting THEN GOTO CloseKeyStream; h.serverStream.PutChar[h.keyStream.GetChar[]]; count _ count + 1; IF count >= 50 THEN { h.serverStream.Flush[]; count _ 0; }; ENDLOOP; GOTO CloseKeyStream; EXITS CloseKeyStream => { IF h.keyStream # NIL THEN{ h.keyStream.Close[]; h.keyStream _ NIL; }; IF count # 0 THEN h.serverStream.Flush[]; }; }; }; StartUp: PROC [h: Handle] = {{ ENABLE UNWIND => h.state _ idle; h.state _ starting; IF NOT h.useOldHost OR h.serverName.Length[] = 0 THEN h.serverName _ FindHostName[h]; h.useOldHost _ FALSE; h.pleaseStop _ FALSE; h.sToUStopped _ FALSE; h.uToSStopped _ FALSE; IF h.setSelection THEN ViewerTools.SetSelection[h.ts, NIL]; h.setSelection _ FALSE; TRUSTED { h.out.PutF["\nViewers Chat of %t.\n", IO.time[Loader.BCDBuildTime[FindHostName]]]; }; IF h.logStream # NIL THEN h.out.PutF["Log file: %g\n", IO.rope[h.logFileName]] ELSE h.out.PutF["No log file.\n"]; OpenConnection[h]; IF h.serverStream = NIL THEN { h.state _ idle; RETURN; }; SetName[h, Rope.Cat["Chat ", h.serverName]]; InitialNegotiations[h]; FromKeys[h]; h.state _ running; h.serverToUserProcess _ FORK ServerToUser[h]; }; }; FindHostName: PROC [h: Handle] RETURNS [host: Rope.ROPE] = { caret: TiogaOps.Location; r: Rope.ROPE; i: INT; r _ ViewerTools.GetSelectionContents[]; IF r.Length[] > 1 THEN RETURN[r]; caret _ TiogaOps.GetCaret[]; r _ TiogaOps.GetRope[caret.node]; i _ caret.where; WHILE i > 0 DO char: CHARACTER = Rope.Fetch[r, i - 1]; IF -- char # '* AND -- NOT ChatTokenProc[char] = other THEN EXIT; i _ i -1; ENDLOOP; host _ Rope.Substr[base: r, start: i, len: caret.where - i]; }; ChatTokenProc: IO.BreakProc -- [char: CHAR] RETURNS [IO.CharClass] -- = TRUSTED { IF IO.TokenProc[char] = other THEN RETURN [other]; IF char = '+ THEN RETURN [other]; RETURN [sepr]; }; OpenConnection: PROC [h: Handle] = TRUSTED { addr: PupDefs.PupAddress; PupDefs.PupPackageMake[]; { h.out.PutF["Opening connection to %g ... ", IO.rope[h.serverName]]; addr _ PupStream.GetPupAddress[PupTypes.telnetSoc, h.serverName ! PupStream.PupNameTrouble => { h.out.PutF["PUP name error"]; FinishStringWithErrorMsg[h, e]; GOTO Return; }]; h.serverStream _ PupStream.PupByteStreamCreate [addr, PupStream.SecondsToTocks[1] ! PupStream.StreamClosing => { h.out.PutF["Can't connect"]; FinishStringWithErrorMsg[h, text]; GOTO Return; }]; h.out.PutF["open.\n"]; EXITS Return => NULL; }; }; CloseConnection: PROC [h: Handle, print: BOOL] = TRUSTED { h.pleaseStop _ TRUE; IF h.serverStream # NIL THEN { IF print THEN h.out.PutF["\nClosing connection to %s", IO.rope[h.serverName] ! IO.Error => CONTINUE]; h.serverStream.Close[]; h.serverStream _ NIL; -- could cause pointer faults! IF print THEN h.out.PutF[" ... Closed\n" ! IO.Error => CONTINUE]; PupDefs.PupPackageDestroy[]; }; IF h.keyStream # NIL THEN { h.keyStream.Close[]; h.keyStream _ NIL; }; IF h.state = running THEN JOIN h.serverToUserProcess; IF h.logStream # NIL THEN h.logStream.Flush[]; h.state _ idle; SetName[h, "Chat"]; }; SetName: PROC [h: Handle, r: Rope.ROPE] = { InternalSetName: PROC [v: ViewerClasses.Viewer] = { v.name _ r; ViewerOps.PaintViewer[viewer: v, hint: caption]; }; EnumerateSplits[h.ts, InternalSetName ! ANY => CONTINUE]; }; ChatMain: Commander.CommandProc = TRUSTED { h: Handle _ NEW[ChatInstanceRecord _ []]; execOut: IO.STREAM _ cmd.out; switchChar: CHAR; i: NAT _ 2; h.argv _ CommandTool.Parse[cmd ! CommandTool.Failed => {msg _ errorMsg; CONTINUE; }]; IF h.argv = NIL THEN RETURN; h.lorc _ 'l; -- default GV login WHILE i < h.argv.argc DO IF h.argv[i].Length[] > 1 THEN SELECT h.argv[i].Fetch[0] FROM '- => { switchChar _ h.argv[i].Fetch[1]; SELECT switchChar FROM 'd => h.destroyOnClose _ TRUE; 'k => { IF i+1 < h.argv.argc THEN { h.keyStream _ IO.RIS[h.argv[i+1]]; i _ i + 1; }; }; 's => h.synchronous _ TRUE; ENDCASE => h.lorc _ switchChar; }; '> => h.logFileName _ Rope.Substr[base: h.argv[i], start: 1]; '< => h.keyFile _ Rope.Substr[base: h.argv[i], start: 1]; ENDCASE => execOut.PutF["chat: unknown command: %s.\n", IO.rope[h.argv[i]]]; i _ i + 1; ENDLOOP; IF h.logFileName.Length[] = 0 THEN { h.logFileName _ IO.PutFR["Chat%d.log", IO.int[logFileNumber]]; logFileNumber _ logFileNumber + 1; }; IF h.keyFile.Length[] > 0 THEN h.keyStream _ FS.StreamOpen[fileName: h.keyFile ! FS.Error => IF error.group = user THEN { execOut.PutF["Chat: Cannot open %s\n", IO.rope[h.keyFile]]; CONTINUE; }]; h.ts _ TypeScript.Create[info: [name: "Chat", iconic: h.argv.argc <= 1 OR h.lorc = 'i], paint: TRUE]; TypeScript.ChangeLooks[h.ts, 'f]; h.ts.file _ h.logFileName; h.ts.icon _ typescript; h.logStream _ FS.StreamOpen[fileName: h.logFileName, accessOptions: $create ! FS.Error => IF error.group = user THEN { execOut.PutF["Chat: Cannot open %s\n", IO.rope[h.logFileName]]; CONTINUE; }]; chatTipTable.link _ h.ts.tipTable; chatTipTable.opaque _ FALSE; h.tipTable _ h.ts.tipTable _ chatTipTable; [in: h.in, out: h.origOut] _ ViewerIO.CreateViewerStreams[name: "Chat.log", viewer: h.ts, editedStream: FALSE]; h.out _ h.origOut; IF h.logStream # NIL THEN h.out _ IOClasses.CreateDribbleOutputStream[output1: h.origOut, output2: h.logStream]; EditedStream.SetEcho[h.in, NIL]; IF h.argv.argc > 1 THEN { h.serverName _ h.argv[1]; h.useOldHost _ TRUE; IF h.lorc = 'i THEN h.lorc _ 'c ELSE TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: IF h.lorc = 'c THEN ConnectChar ELSE LoginChar]; }; IF List.Length[chatInstanceList] = 0 THEN { destroyEvent _ ViewerEvents.RegisterEventProc[proc: MyDestroy, event: destroy] }; chatInstanceList _ CONS[h, chatInstanceList]; Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "Disconnect", proc: MyDisconnect, clientData: h, fork: TRUE, documentation: "Close Ethernet connection"]]; Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "Login", proc: MyLogin, clientData: h, documentation: "Open Ethernet Connection to selected host and send Login sequence"]]; Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "Connect", proc: MyConnect, clientData: h, documentation: "Open Ethernet Connection to selected host"]]; Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "BreakKey", proc: MyBreakKey, clientData: h, documentation: "Transmit Ascii.NULL (DLS interprets as RS-232 Line Break)"]]; Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "FlushLog", proc: MyFlushLog, clientData: h, documentation: "Flush log file to disk."]]; h.oldSplit _ Menus.FindEntry[menu: h.ts.menu, entryName: "Split"]; Menus.ReplaceMenuEntry[menu: h.ts.menu, oldEntry: h.oldSplit, newEntry: Menus.CreateEntry[name: "Split", proc: MySplit, fork: TRUE, clientData: h, documentation: "Split window"]]; ViewerOps.AddProp[h.ts, $ChatToolData, h]; ViewerOps.PaintViewer[viewer: h.ts, hint: all]; IF h.synchronous THEN UserToServer[h] ELSE { h.userToServerProcess _ FORK UserToServer[h]; Process.Detach[h.userToServerProcess]; }; }; UserToServer: PROC [h: Handle] = TRUSTED {{ char: CHAR; count: NAT _ 0; DO ENABLE { ABORTED => ERROR; -- ever happen? IO.Error => { CloseConnection[h, FALSE]; GOTO Die; }; PupStream.StreamClosing => { IF h.serverStream # NIL THEN { h.out.PutF["\nConnection being closed by %s", IO.rope[h.serverName]]; FinishStringWithErrorMsg[h, text]; CloseConnection[h, FALSE]; IF h.destroyOnClose THEN { ViewerOps.DestroyViewer[h.ts]; GOTO Die; }; }; CONTINUE; }; }; char _ h.in.GetChar[]; IF h.inDestroy THEN { CloseConnection[h, FALSE]; GOTO Die; }; SELECT char FROM AbortChar => { IF h.state = running THEN CloseConnection[h, TRUE]; }; DisconnectChar => { IF h.state = running THEN CloseConnection[h, TRUE]; }; RemoteCloseChar => { ERROR PupStream.StreamClosing[why: remoteClose, text: NIL]; }; ConnectChar => { IF h.state = idle THEN { h.lorc _ 'c; StartUp[h]; count _ 0; }; }; LoginChar => { IF h.state = idle THEN { h.lorc _ 'l; StartUp[h]; count _ 0; }; }; ENDCASE => { SELECT h.state FROM running => { h.serverStream.PutChar[char]; IF count >= 50 OR h.in.CharsAvail[] = 0 THEN { h.serverStream.Flush[]; count _ 0; } ELSE count _ count + 1; }; ENDCASE => h.out.PutChar[char]; }; ENDLOOP; EXITS Die => { IF h.logStream # NIL THEN h.logStream.Close[]; }; }; }; MyDestroy: ViewerEvents.EventProc = { h: Handle _ NARROW[ViewerOps.FetchProp[viewer, $ChatToolData]]; IF h = NIL THEN RETURN; IF NumSplit[viewer] = 1 THEN { -- last one h.inDestroy _ TRUE; chatInstanceList _ List.Remove[h, chatInstanceList]; IF List.Length[chatInstanceList] = 0 THEN { ViewerEvents.UnRegisterEventProc[proc: destroyEvent, event: destroy]; }; } ELSE IF NumSplit[viewer] > 1 THEN { IF viewer = h.ts THEN { Another: PROC [v: ViewerClasses.Viewer] = { IF v # viewer THEN h.ts _ v; }; EnumerateSplits[viewer, Another]; }; }; }; MyClose: ViewerEvents.EventProc = { h: Handle _ NARROW[ViewerOps.FetchProp[viewer, $ChatToolData]]; IF h = NIL THEN RETURN; TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: DisconnectChar]; }; MyConnect: Menus.MenuProc = { viewer: TypeScript.TS _ NARROW[parent]; h: Handle _ NARROW[clientData]; h.ts _ viewer; -- "primary" copy h.useOldHost _ mouseButton # red; TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: ConnectChar]; }; MyLogin: Menus.MenuProc = { viewer: TypeScript.TS _ NARROW[parent]; h: Handle _ NARROW[clientData]; h.ts _ viewer; -- "primary" copy h.useOldHost _ mouseButton # red; TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: LoginChar]; }; MyDisconnect: Menus.MenuProc = { h: Handle _ NARROW[clientData]; TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: DisconnectChar]; }; MyBreakKey: Menus.MenuProc = TRUSTED { h: Handle _ NARROW[clientData]; IF h.state # running THEN RETURN; IF h.serverStream # NIL THEN { h.serverStream.PutChar['\000]; h.serverStream.Flush[]; }; }; MyFlushLog: Menus.MenuProc = { h: Handle _ NARROW[clientData]; IF h.logStream # NIL THEN h.logStream.Flush[]; }; MySplit: Menus.MenuProc = { h: Handle _ NARROW[clientData]; CheckChatProperties: PROC [v: ViewerClasses.Viewer] = { IF ViewerOps.FetchProp[v, $ChatToolData] = NIL THEN ViewerOps.AddProp[v, $ChatToolData, h]; v.tipTable _ h.tipTable; }; h.oldSplit.proc[parent: parent, clientData: h.oldSplit.clientData, mouseButton: mouseButton, shift: shift, control: control]; EnumerateSplits[NARROW[parent, ViewerClasses.Viewer], CheckChatProperties]; }; ConnectionOpen: TIPUser.TIPPredicate --PROC RETURNS [BOOLEAN]-- = { h: Handle; viewer: ViewerClasses.Viewer _ ViewerTools.GetSelectedViewer[]; IF viewer=NIL THEN RETURN [FALSE]; -- no primary selection h _ NARROW[ViewerOps.FetchProp[viewer, $ChatToolData]]; IF h=NIL THEN RETURN [FALSE]; -- not a chat tool RETURN [h.state = running]; -- connection open? }; EnumerateSplits: PROC [v: ViewerClasses.Viewer, p: PROC [v: ViewerClasses.Viewer]] = { v2: ViewerClasses.Viewer _ v; IF v = NIL THEN RETURN; DO p[v2]; IF v2.link = NIL OR v2.link = v THEN RETURN; v2 _ v2.link; ENDLOOP; }; NumSplit: PROC [v: ViewerClasses.Viewer] RETURNS [count: INT _ 0] = { Counter: PROC [v2: ViewerClasses.Viewer] = { count _ count + 1; }; EnumerateSplits[v, Counter]; }; Init: PROC = { Commander.Register[key: "Chat", proc: ChatMain, doc: "Pup User Telnet, see ChatDoc.tioga"]; CommandExtras.MakeUninterpreted[Commander.Lookup["Chat"]]; chatTipTable _ TIPUser.InstantiateNewTIPTable["Chat.TIP"]; TIPUser.RegisterTIPPredicate[$ConnectionOpen, ConnectionOpen]; }; Init[]; END. March 31, 1982 9:27 pm, Stewart, copied from Laurel Chat April 1, 1982 4:06 pm, Stewart, own viewer class & TIP table April 4, 1982 5:18 pm, Stewart, Menu April 6, 1982 10:27 pm, Stewart, command line stuff 18-Apr-82 17:42:24, Use TypeScript again June 6, 1982 4:44 pm, Stewart, fix Menu instantiation to add Split June 6, 1982 8:08 pm, Stewart, using own Split code until viewers copies PropList September 21, 1982 4:26 pm, Stewart, Cedar 3.4 November 3, 1982 5:53 pm, Warren Teitelman January 23, 1983 10:18 pm, Stewart, Cedar 3.5, major rework January 24, 1983 5:49 pm, Stewart, Cedar 3.6 January 25, 1983 3:09 pm, Maxwell April 6, 1983 6:43 pm, Larry Stewart June 13, 1983 1:41 pm, Stewart, Cedar 4.2, added synchronous feature September 7, 1983 6:19 pm, Stewart, Cedar 5 0Chat.mesa Larry Stewart, December 14, 1983 2:32 pm Warren Teitelman, November 3, 1982 5:53 pm Last Edited by: Maxwell, January 25, 1983 3:09 pm Last Edited by: Paul Rovner, November 29, 1983 10:56 am This procedure is FORKed by the UserToServer process at the time a connection is opened. It is JOINED whenever the connection is closed. It also goes away if the Chat viewer is destroyed. buffer: REF TEXT _ NEW[TEXT[400]]; buffer.length _ 0; buffer.length _ h.serverStream.GetBlock[block: buffer, startIndex: 0, stopIndexPlusOne: 400 ! PupStream.TimeOut => { IF h.pleaseStop OR h.state # running THEN GOTO Cleanup ELSE RESUME; }]; IF h.pleaseStop OR h.state # running THEN GOTO Cleanup; IF h.out.UserAbort[] THEN ERROR; FOR i: NAT IN [0.. buffer.length) DO IF buffer[i]#Ascii.LF THEN h.out.PutChar[Inline.BITAND[buffer[i], 177B]]; ENDLOOP; PupStream.SendMark[h.serverStream, setPageLength]; PupStream.SendMark[h.serverStream, 255C]; kludge to minimize BELLS from Juniper UserAbort is active because Chat.TIP is not activated until h.state = running (due to the TIP predicate), so control-DEL sets UserAbort. However, while in this routine, UserAbort is used only to stop taking characters from the keyStream, not to close the connection. If the user types control-DEL here, she will have to type it again to really close the connection. This procedure includes '+ in order to handle Pup names of the form dls+100004 iconic unless command line not empty create log file plug in Chat TIP table. IF -i then we are just initializing the host name, else really connect closeEvent _ ViewerEvents.RegisterEventProc[proc: MyClose, event: close]; last viewer destroyed ! Close connection and exit This EventProc exits only to keep h.ts pointing to a valid copy of the typescript viewer. It is only needed for the use of TypeScript.InsertCharAtFrontOfBuffer. next line is a crock to avoid signal in TypeScripts, see McGregor Process.Pause[Process.SecondsToTicks[2]]; h.out.Close[]; breaks viewers destroy!! ViewerEvents.UnRegisterEventProc[proc: closeEvent, event: close]; main program for chat Êã˜Jšœ ™ Jšœ(™(Jšœ*™*Jšœ1™1Jšœ7™7J˜šÏk ˜ Jšœœœ˜Jšœœœ˜Jšœ œ!˜0Jšœœ˜(Jšœ œ!˜2Jšœ œ ˜Jšœœ˜Jšœ˜Jšœ œ˜,Jšœœ˜Jšœœ˜JšœœR˜]Jšœœ ˜Jšœœ1˜>Jšœ œu˜„Jšœ œ ˜Jšœœœ ˜4Jšœ œ˜-JšœœH˜UJšœ œ2œ˜FJšœœ˜J˜Jšœ œH˜ZJšœ œ˜%Jšœ œ2˜Ašœ œ9˜JJ˜——Jš œœœœœœ ˜0šœ/˜6Jšœœœ!˜5J˜J˜Jšœ˜J˜.—Jšœ˜Jš˜J˜Jšœœœ˜&Jšœœ/˜:J˜Jšœœ˜Jšœ œ˜Jšœ œ˜Jšœ œ˜Jšœœ˜Jšœ œ ˜J˜J˜J˜J˜J˜šœœ œœ˜-JšœœÏc˜,J˜Jšœœ˜Jšœœ˜Jšœ œœ˜Jšœœ˜Jšœ!˜!Jšœ œœ˜Jšœ œœ˜Jšœ œœ˜Jšœ œœ˜Jšœœ˜Jšœœ˜Jšœœ ˜Jšœ œœ˜Jšœ œœ˜Jšœœœ˜Jšœ œœ˜Jšœœœ˜Jšœœœ˜Jšœ œœ˜Jšœœœ˜J˜Jšœœœœ˜Jšœ˜J˜J˜—Jšœœ˜Jš œœœœœœ˜(Jšœ/œ˜3Jšœ-œ˜1J˜J˜JšœB™BJšœF™FJšœ2™2J˜JšÏn œœ˜#šœ"™"Jšœœ˜J˜š˜šœ˜Jšœœ ˜˜JšœœœG˜_Jšœ ˜ J˜—Jšœ œ ˜J˜—šœœ˜ J˜.Jšœœ5˜OJ˜———Jšœ™Jšœ[™[Jšœ™Jšœ6™6Jšœ ™ Jšœ™Jšœ7™7Jšœ ™ Jšœ$™$JšœI™Išœ™˜˜Jšœœœœ˜6Jšœœ˜ J˜—šœ˜Jšœ˜J˜——Jšœœœœ ˜7šœ ˜Jš œœœœœ œ˜O—Jšœ˜Jš˜Jšœœ˜ J˜J˜J˜—šŸœœœ˜1J˜2J˜Jšœ3™3JšœO™Ošœ œ œ˜ Jšœœ˜J˜9J˜!J˜Jšœ œ#œ˜VJ˜J˜!J˜J˜J˜—J˜J˜šŸœœœ˜CJšœ œœœ˜?Jšœ˜J˜J˜——JšœU™UJšœ2™2JšœU™UJšœT™TJšœ:™:˜šŸœœœ˜&Jšœœ˜J˜šœœœ˜3Jšœœœœ˜?J˜.J˜šœ œ˜J˜J˜ J˜—Jšœ˜—Jšœ˜Jš˜˜šœœœ˜J˜Jšœœ˜J˜—Jšœ œ˜)J˜—J˜J˜J˜—šŸœœ˜Jšœœ˜ J˜Jšœœœœ ˜UJšœœ˜Jšœœ˜Jšœœ˜Jšœœ˜Jšœœ œ˜;Jšœœ˜šœ˜ Jšœ&œ*˜RJ˜—Jšœœœœ˜NJšœ˜"J˜šœœœ˜J˜Jšœ˜J˜—J˜,J˜J˜ J˜Jšœœ˜-J˜J˜J˜—šŸ œœ œ œ˜J˜"J˜J˜—š œœœ"œ œœ˜yJšœ'œ˜;Jšœ˜ J˜—Jšœ$™$JšœGœœ˜eJ˜!J˜J˜Jšœ™š œœ>œ œœ˜vJšœ'œ˜?Jšœ˜ J˜J˜—Jšœ™J˜"Jšœœ˜J˜*Jšœhœ˜oJ˜JšœœœW˜pJšœœ˜ šœœ˜J˜Jšœœ˜JšœF™FJšœ œ ˜Jšœ6œ œ œ ˜jJ˜—šœ#œ˜+JšœI™IJ˜NJ˜—Jšœœ˜-Jšœ}œ/˜°J˜ÂJ˜®J˜ÀJ˜žJ˜J˜BJšœ~œ1˜³J˜*J˜/Jšœœ˜%šœ˜Jšœœ˜-J˜&J˜—J˜J˜šŸ œœœ˜+Jšœœ˜ Jšœœ˜šœœ˜ Jšœœž˜"šœ ˜ Jšœ™Jšœ™Jšœœ˜Jšœ˜ J˜—˜šœœœ˜Jšœ.œ˜EJ˜"Jšœœ˜šœœ˜J˜Jšœ˜ J˜—J˜—Jšœ˜ J˜—J˜J˜šœ œ˜Jšœœ˜Jšœ˜ J˜—šœ˜˜Jšœœœ˜3J˜—˜Jšœœœ˜3J˜—˜Jšœ1œ˜;J˜—˜šœœ˜J˜ J˜ J˜ J˜—J˜—˜šœœ˜J˜ J˜ J˜ J˜—J˜—šœ˜ šœ ˜˜ J˜šœ œœ˜.J˜J˜ J˜—Jšœ˜J˜—Jšœ˜—J˜——Jšœ˜—Jš˜˜Jšœœœ˜.J˜—J˜J˜J˜—JšœQ™QJšœO™OJ˜˜%Jšœ œ-˜?Jšœœœœ˜šœœž ˜*Jšœœ˜JšœA™AJšœ)™)Jšœ'™'J˜4šœ#œ˜+J˜EJšœA™AJ˜—J˜—šœœœ˜#šœœ˜šŸœœ˜+Jšœ œ ˜J˜—J˜!J˜—J˜—J˜J˜—˜#Jšœ œ-˜?Jšœœœœ˜J˜EJ˜J˜—˜Jšœœœ ˜'Jšœ œ ˜Jšœž˜ J˜"J˜BJ˜J˜—˜Jšœœœ ˜'Jšœ œ ˜Jšœž˜ J˜"J˜@J˜J˜—˜ Jšœ œ ˜J˜EJ˜J˜—šœœ˜&Jšœ œ ˜Jšœœœ˜!šœœœ˜J˜J˜J˜—J˜J˜—˜Jšœ œ ˜Jšœœœ˜.J˜J˜——˜Jšœ œ ˜šŸœœ˜7Jšœ)œœ(˜[J˜J˜—J˜}Jšœœ5˜KJ˜J˜—šœ%žœ˜CJ˜ J˜?Jš œœœœœž˜:Jšœœ-˜7Jš œœœœœž˜0Jšœž˜/J˜J˜—šŸœœœ˜VJ˜Jšœœœœ˜š˜J˜Jš œ œœ œœ˜,J˜ Jšœ˜—J˜J˜—šŸœœœ œ ˜EšŸœœ˜,J˜J˜—J˜J˜J˜—šŸœœ˜J˜[Jšœ:˜:J˜:J˜>J˜J˜—Jšœ™J˜J˜J˜Jšœ˜J˜J˜8Jšœ3œ˜