DIRECTORY Atom USING [ GetProp, PutProp ], Commander USING [ CommandProc, Register ], CommandTool USING [ NextArgument ], FinchSmarts USING [ ConvDesc, GetNumbersForRName, PlaceCall ], FinchTool USING [ CheckActive, FeepValue, finchQueue, finchToolHandle, GetSelectedDesc, Report, Status ], FS USING [ ComponentPositions, Error, ExpandName, FileInfo ], Icons USING [ IconFlavor, NewIconFromFile ], IO, MBQueue USING [QueueClientAction], NodeProps USING [ GetProp, PutProp ], Rope, TEditDocument USING [ TEditDocumentData ], TextNode USING [ Ref, StepForward ], Thrush USING [ ConversationID, nullConvID ], TiogaButtons USING [ CreateButtonFromNode, CreateViewer, FindTiogaButton, GetRope, LoadViewer, TextNodeRef, TiogaButton, TiogaButtonProc, TiogaOpsRef ], TiogaOps USING [ Ref, Root, SetSelection ], UserProfile USING [ CallWhenProfileChanges, ProfileChangedProc, Token ], ViewerClasses USING [ Column, Viewer, ViewerRec ], ViewerEvents USING [ EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc ], ViewerOps USING [ AddProp, FetchProp, DestroyViewer ], ViewerTools USING [ SetContents ], VoiceUtils USING [ Registrize ] ; FinchDirectoryImpl: CEDAR PROGRAM IMPORTS Atom, Commander, CommandTool, Rope, FinchSmarts, FinchTool, FS, Icons, IO, MBQueue, NodeProps, TextNode, TiogaButtons, TiogaOps, UserProfile, ViewerEvents, ViewerOps, ViewerTools, VoiceUtils EXPORTS FinchTool = { OPEN FinchTool, IO; ROPE: TYPE = Rope.ROPE; Viewer: TYPE = ViewerClasses.Viewer; labelledDirectoryIcon: Icons.IconFlavor _ tool; DirectoryInstance: TYPE = REF DirectoryInstanceBody; DirectoryInstanceBody: TYPE = RECORD [ rName: Rope.ROPE_NIL, name: Rope.ROPE_NIL, number: Rope.ROPE_NIL, homeNumber: Rope.ROPE_NIL, remarks: Rope.ROPE _ NIL, where: Where _ office, -- Oh, for closures! filled: BOOL_FALSE ]; Where: TYPE = { office, home }; DirectoryButtonNotifier: TiogaButtons.TiogaButtonProc = { directoryInstance: DirectoryInstance; SelectEntryInDirectory[button]; directoryInstance _ IF button.clientData = NIL THEN (button.clientData _ NEW[DirectoryInstanceBody _ []]) ELSE NARROW[button.clientData]; IF control OR mouseButton = red THEN RETURN; directoryInstance.where _ SELECT mouseButton FROM blue=>home, ENDCASE=>office; MBQueue.QueueClientAction[finchQueue, DirectoryButton, button]; }; SelectEntryInDirectory: PROC[button: TiogaButtons.TiogaButton] = { root: TiogaOps.Ref = TiogaOps.Root[button.startLoc.node]; v: ViewerClasses.Viewer = NARROW[NodeProps.GetProp[TiogaButtons.TextNodeRef[root], $Viewer]]; IF v=NIL THEN ERROR; TiogaOps.SetSelection[ viewer: v, start: [button.startLoc.node, 0], end: [button.endLoc.node, TiogaButtons.GetRope[button].Length[]], level: node, which: feedback ]; }; DirectoryButton: PROC[r: REF ANY] = { button: TiogaButtons.TiogaButton = NARROW[r]; directoryInstance: DirectoryInstance _ FillDirectoryInstance[button]; convDesc: FinchSmarts.ConvDesc = FinchTool.GetSelectedDesc[]; IF convDesc#NIL AND convDesc.situation.self.state > $parsing THEN FinchTool.Report["Call already in progress"] ELSE CallNumber[directoryInstance, directoryInstance.where, convDesc]; }; CallByDescription: PUBLIC PROC[description: ROPE, residence: BOOL] = { rName: ROPE_NIL; instance: DirectoryInstance; number: ROPE_NIL; convDesc: FinchSmarts.ConvDesc = FinchTool.GetSelectedDesc[]; convID: Thrush.ConversationID = IF convDesc=NIL THEN Thrush.nullConvID ELSE convDesc.situation.self.convID; IF description#NIL AND description.Length#0 THEN SELECT description.Fetch[0] FROM IN ['A..'Z], IN ['a..'z] => rName _ description; ENDCASE => number _ description; IF rName # NIL THEN IF residence AND (finchToolHandle.directories=NIL) THEN FinchTool.Report["No directory viewer is open, so residence can't be found"] ELSE instance _ FindNamedInstance[rName]; IF instance # NIL THEN CallNumber[instance, IF residence THEN home ELSE office, convDesc] ELSE IF residence THEN FinchTool.Report["Residence number not found"] ELSE IF FinchTool.CheckActive[FinchTool.finchToolHandle] THEN FinchSmarts.PlaceCall[ convID: convID, rName: rName, number: FinchTool.FeepValue[number], useNumber: rName=NIL]; }; FindNamedInstance: PROC[name: Rope.ROPE] RETURNS [ instance: DirectoryInstance_NIL ] = { FOR dL: LIST OF ViewerClasses.Viewer _ finchToolHandle.directories, dL.rest WHILE dL#NIL DO rName: Rope.ROPE = VoiceUtils.Registrize[name]; root: TextNode.Ref; v: Viewer = dL.first; IF v=NIL THEN LOOP; root _ NARROW[v.data, TEditDocument.TEditDocumentData].text; FOR node: TextNode.Ref _ TextNode.StepForward[root], TextNode.StepForward[node] WHILE node#NIL DO button: TiogaButtons.TiogaButton = TiogaButtons.FindTiogaButton[v, [TiogaButtons.TiogaOpsRef[node], 0]]; instance _ IF button=NIL THEN NIL ELSE FillDirectoryInstance[button]; IF instance=NIL THEN LOOP; IF ~rName.Equal[s2: instance.rName, case: FALSE] AND ~instance.name.Find[s2: name, case: FALSE]>=0 THEN { instance_NIL; LOOP; }; SelectEntryInDirectory[button]; RETURN[instance]; ENDLOOP; ENDLOOP; }; CallNumber: PROC[ instance: DirectoryInstance, where: Where, convDesc: FinchSmarts.ConvDesc] = { name, number: Rope.ROPE; convID: Thrush.ConversationID = IF convDesc=NIL THEN Thrush.nullConvID ELSE convDesc.situation.self.convID; IF instance=NIL OR ~FinchTool.CheckActive[FinchTool.finchToolHandle] THEN RETURN; name _ IF instance.rName#NIL THEN instance.rName ELSE instance.name; number _ IF where=home THEN instance.homeNumber ELSE instance.number; IF where=home AND Rope.Equal[number,""] THEN { FinchTool.Status[IO.PutFR["No home number for %s", rope[name]]]; } ELSE { contents: ROPE _ name; FinchTool.Status[IO.PutFR["Placing call to %s (%s)", rope[name], rope[number]]]; IF where=home THEN contents _ contents.Concat[" at home"]; ViewerTools.SetContents[finchToolHandle.calledPartyText, contents]; FinchSmarts.PlaceCall[convID: convID, rName: name, number: FinchTool.FeepValue[number], useNumber: where=home OR instance.rName=NIL]; }; }; PhoneQueryCmd: Commander.CommandProc = { name: Rope.ROPE _ CommandTool.NextArgument[cmd]; number, homeNumber: ROPE; rName: ROPE _ name; instance: DirectoryInstance _ FindNamedInstance[name]; IF instance = NIL THEN instance _ NEW[DirectoryInstanceBody_[name: name]] ELSE IF instance.rName#NIL THEN rName _ instance.rName; IF instance.rName#NIL OR ~instance.filled THEN { [rName, number, homeNumber] _ FinchSmarts.GetNumbersForRName[rName: rName]; IF rName#NIL THEN instance.rName _ rName; IF number#NIL AND instance.number=NIL THEN instance.number _ number; IF homeNumber#NIL AND instance.homeNumber=NIL THEN instance.homeNumber _ homeNumber; }; IO.PutF[cmd.out, "%g (%g): %g, %g\n", rope[instance.name], rope[instance.rName], rope[instance.number], rope[instance.homeNumber]]; }; RebuildDirectories: UserProfile.ProfileChangedProc = { IF reason#firstTime AND finchToolHandle#NIL AND finchToolHandle.directories#NIL THEN BuildDirectories[]; }; BuildDirectories: PUBLIC PROC = { defaultFiles: Rope.ROPE = "User.TDir"; stream: IO.STREAM; directoryFiles: Rope.ROPE _ UserProfile.Token[key: "FinchTelephoneDirectory", default: defaultFiles]; IF directoryFiles = defaultFiles THEN directoryFiles _ UserProfile.Token[key: "Finch.TelephoneDirectory", default: "User.TDir"]; stream _ IO.RIS[directoryFiles]; UNTIL IO.EndOf[stream] DO nextDirectoryFile: Rope.ROPE _ IO.GetLineRope[stream]; IF NOT Rope.Equal[nextDirectoryFile,""] THEN BuildDirectoryDisplayer[nextDirectoryFile]; ENDLOOP; }; DestroyDirectories: PUBLIC PROC = { IF finchToolHandle = NIL THEN RETURN; FOR dL: LIST OF ViewerClasses.Viewer _ finchToolHandle.directories, dL.rest WHILE dL#NIL DO ViewerOps.DestroyViewer[dL.first]; ENDLOOP; finchToolHandle.directories _ NIL; }; BuildDirectoryDisplayer: PROC[directoryFile: Rope.ROPE, newOK: BOOL_TRUE] = { root: TextNode.Ref; lastDir: LIST OF ViewerClasses.Viewer _ NIL; msV: ViewerClasses.Viewer _ NIL; nameWOVersion: Rope.ROPE _ NameWithoutVersion[directoryFile]; baseName: Rope.ROPE _ BaseName[directoryFile]; fullName: Rope.ROPE; IF nameWOVersion=NIL THEN RETURN; fullName _ FS.FileInfo[name: nameWOVersion! FS.Error=> IF error.code=$unknownFile OR error.code=$illegalName THEN CONTINUE].fullFName; IF finchToolHandle = NIL THEN RETURN; FOR dL: LIST OF ViewerClasses.Viewer _ finchToolHandle.directories, dL.rest WHILE dL#NIL DO lastDir _ dL; IF nameWOVersion.Equal[s2: NameWithoutVersion[dL.first.file], case: FALSE] THEN { msV _ dL.first; IF fullName.Equal[dL.first.file, FALSE] THEN RETURN ELSE EXIT; }; ENDLOOP; IF msV=NIL THEN { IF ~newOK THEN RETURN; msV_ TiogaButtons.CreateViewer[ info: [name: Rope.Concat["Telephone Directory: ", fullName], column: left, menu: NIL, iconic: FALSE, icon: labelledDirectoryIcon, label: baseName]]; ViewerOps.AddProp[msV, $DestroyRegistration, ViewerEvents.RegisterEventProc[HandleViewerDestroy, destroy, msV, TRUE]]; IF finchToolHandle.directories = NIL THEN Atom.PutProp[$FinchRegistrations, $SaveRegistration, ViewerEvents.RegisterEventProc[ proc: HandleViewerChange, event: save, filter: $Text, before: FALSE]]; IF lastDir#NIL THEN lastDir.rest _ LIST[msV] ELSE finchToolHandle.directories _ LIST[msV]; } ELSE msV.name _ Rope.Concat["Telephone Directory: ", fullName]; TiogaButtons.LoadViewer[msV, fullName]; root _ NARROW[msV.data, TEditDocument.TEditDocumentData].text; NodeProps.PutProp[root, $Viewer, msV]; -- since Button procs don't get you to the viewer. FOR node: TextNode.Ref _ TextNode.StepForward[root], TextNode.StepForward[node] WHILE node#NIL DO commentProp: REF BOOL _ NARROW[NodeProps.GetProp[node, $Comment]]; IF commentProp#NIL AND commentProp^ THEN LOOP; []_TiogaButtons.CreateButtonFromNode[ node: TiogaButtons.TiogaOpsRef[node], proc: DirectoryButtonNotifier, fork: FALSE]; ENDLOOP; }; NameWithoutVersion: PROC [shortName: Rope.ROPE] RETURNS [fullNameWithoutVersion: Rope.ROPE] = { cp: FS.ComponentPositions; [fullNameWithoutVersion, cp, ] _ FS.ExpandName[shortName, "///"! FS.Error => IF error.code = $illegalName THEN CONTINUE]; IF fullNameWithoutVersion = NIL THEN RETURN; IF cp.ver.length # 0 THEN fullNameWithoutVersion _ fullNameWithoutVersion.Substr[len: cp.ver.start-1]; }; BaseName: PROC [longName: Rope.ROPE] RETURNS [shortName: Rope.ROPE] = { cp: FS.ComponentPositions; fullName: Rope.ROPE; [fullName, cp, ] _ FS.ExpandName[longName ! FS.Error => IF error.code = $illegalName THEN CONTINUE]; IF fullName = NIL THEN RETURN; shortName _ fullName.Substr[start: cp.base.start, len: cp.base.length]; IF cp.ext.length # 0 THEN shortName _ Rope.Cat[shortName, ".", fullName.Substr[start: cp.ext.start, len: cp.ext.length]]; }; HandleViewerDestroy: ViewerEvents.EventProc = { lastDir: LIST OF ViewerClasses.Viewer_NIL; eR: ViewerEvents.EventRegistration; IF event#destroy OR finchToolHandle=NIL THEN RETURN; FOR dL: LIST OF ViewerClasses.Viewer _ finchToolHandle.directories, dL.rest WHILE dL#NIL DO IF dL.first = viewer THEN { r: REF = ViewerOps.FetchProp[dL.first, $DestroyRegistration]; IF r#NIL THEN ViewerEvents.UnRegisterEventProc[NARROW[r], destroy]; IF lastDir=NIL THEN finchToolHandle.directories _ dL.rest ELSE lastDir.rest _ dL.rest; EXIT; }; lastDir _ dL; ENDLOOP; IF finchToolHandle.directories #NIL THEN RETURN; eR _ NARROW[Atom.GetProp[$FinchRegistrations, $SaveRegistration]]; IF eR#NIL THEN ViewerEvents.UnRegisterEventProc[eR, save]; }; HandleViewerChange: ViewerEvents.EventProc = { lastDir: LIST OF ViewerClasses.Viewer_NIL; IF event#save THEN RETURN; MBQueue.QueueClientAction[finchQueue, AvoidLockup, viewer]; }; AvoidLockup: PROC[r: REF ANY] = { viewer: ViewerClasses.Viewer = NARROW[r]; BuildDirectoryDisplayer[directoryFile: viewer.file, newOK: FALSE]; }; FillDirectoryInstance: PROC [button: TiogaButtons.TiogaButton] RETURNS[instance: DirectoryInstance] = { entry: Rope.ROPE = TiogaButtons.GetRope[button]; entryStream: IO.STREAM = IO.RIS[entry]; rnameStart, rnameEnd: INT; instance _ NARROW[button.clientData]; IF instance=NIL THEN { instance _ NEW[DirectoryInstanceBody_[]]; button.clientData _ instance; }; IF instance.filled THEN RETURN; instance.filled _ TRUE; IF entry=NIL THEN RETURN; instance.name _ ToTab[entryStream]; instance.number _ ToTab[entryStream]; instance.homeNumber _ ToTab[entryStream]; instance.remarks _ ToTab[entryStream]; rnameStart _ instance.name.Find["<"]; IF rnameStart<0 THEN RETURN; rnameEnd _ instance.name.Find[">", rnameStart]; instance.rName _ VoiceUtils.Registrize[instance.name.Substr[rnameStart+1, rnameEnd-rnameStart-1]]; }; ToTab: PROC[s: IO.STREAM] RETURNS [field: Rope.ROPE_NIL] = { ToTabProc: IO.BreakProc = { RETURN[IF char=IO.TAB OR char=IO.CR THEN sepr ELSE other]; }; field _ s.GetTokenRope[ToTabProc ! IO.EndOfStream=> CONTINUE;].token; IF field.Equal["*"] THEN field_NIL; }; UserProfile.CallWhenProfileChanges[proc: RebuildDirectories]; Commander.Register["Phone?", PhoneQueryCmd, "Lookup phone number"]; labelledDirectoryIcon _ Icons.NewIconFromFile["Finch.icons", 13]; }. FinchDirectoryImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Last Edited by: Swinehart, April 6, 1987 2:25:16 pm PDT Last Edited by: Pier, April 26, 1984 3:42:46 pm PST Polle Zellweger (PTZ) October 15, 1985 4:07:32 pm PDT Types and Typeoids Selecting, Placing calls [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] Check these for new Cedar interp. For each node that's a button, fill if necessary, then examine. This should launch the call Manual Directory Access Creating, Maintaining Directories [reason: UserProfile.ProfileChangeReason] If file is unknown, it will be rediscovered later and complained about. (That's reasonable, since in general a document can be displayed in multiple viewers.) For each non-comment node, make it into a button. Maybe later, distinguish levels. [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL _ FALSE] When a file's saved we look at all our directories, so we only need one save registration. We kill it here when all directories go away so that a new Finch.bcd won't find one in place. [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL _ FALSE] If directory icon is closed (fer sherr) and at other times (probably) the attempt to reload the directory viewer will run into a column lock that we're holding right here. Even though the first mention of locks in ViewerEvents says explicitly that there ain't no such problem! So we use finchQueue to avoid forking off all sorts of totally unsynchronized rebuilding actions. Initialization Swinehart, September 6, 1985 7:24:20 am PDT Eliminate Call, CallHome viewer buttons in favor of select-operations only. Check Finch active before trying calls, in more places. Clean up treatment of "at home" and all that. Use more care in updating called-party field of Finch tool. changes to: BuildDirectoryMenu Swinehart, September 8, 1985 10:54:14 pm PDT Use TiogaButtons. Eliminate column-formatting (user must do it). Eliminate Answer, New Directory. Put each user directory in different viewer, keep track of them for search. Search in User-profile order. Register with: User profile for changes Rollback to update if changed Destroy to remove from list changes to: FinchDirectoryImpl, directoryIcon, directories, DirectoryInstanceBody, Where, PhoneFromSelection, MakeSelected, CallByDescription, FindNamedInstance, CallNumber, NewDirProc, ClearDirectoryWindowButtons, BuildLine (local of BuildDirectoryWindowButtons), BuildDirectoryWindowButtons, BuildDirectoryDisplayer, BuildDirectoryDisplayer, DirectoryButtonNotifier, SelectEntryInDirectory, ToTab, ToTabProc (local of ToTab), DIRECTORY, BuildDirectoryDisplayer Swinehart, September 16, 1985 10:12:14 am PDT Lots of error checks for system-uninitialized problems. changes to: RebuildDirectories, DestroyDirectories, BuildDirectoryDisplayer, HandleViewerDestroy, AvoidLockup, UserProfile Polle Zellweger (PTZ) October 15, 1985 4:00:39 pm PDT Change to directory icon labelled with the short name of the directory file. changes to: labelledDirectoryIcon (def), BuildDirectoryDisplayer, NameWithoutVersion (renamed from BaseName), BaseName (new function), labelledDirectoryIcon (initialization) ΚΙ˜™Icodešœ Οmœ1™Jšœ žœZ˜iJšžœžœ5˜=Jšœžœ!˜,Jšžœ˜Jšœžœ˜"Jšœ žœ˜%J˜Jšœžœ˜*Jšœ žœ˜$Jšœžœ ˜,Jšœ žœ†˜˜Jšœ žœ˜+Jšœ žœ7˜HJšœžœ˜2Jšœ žœJ˜\Jšœ žœ'˜6Jšœ žœ˜"Jšœ˜J˜—J˜šœžœž˜!Jšžœ=žœ žœvžœ˜άJšžœ žœ˜J˜—™J™Jšžœžœžœ˜Jšœžœ˜$J˜Jšœ/˜/J˜Jšœžœžœ˜4šœžœžœ˜&Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜Jšœž ˜Jšœžœžœ˜JšœΟc˜+Jšœžœž˜J˜—J˜Jšœžœ˜J˜—™J™•StartOfExpansion‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]šΟnœ"˜9JšΠck-™-Jš‘Q™QJ˜&Jšœ˜šœžœžœž˜3Jšœžœžœžœ˜U—Jšžœ žœžœžœ˜,Jšœžœ žœ žœ ˜NJšœ?˜?J˜J˜—š œžœ&˜BJ˜9šœ˜Jšžœ=˜C—Jšžœžœžœžœ˜˜J˜,J˜AJ˜—J˜J˜—š œžœžœžœ˜%J™!Jšœ#žœ˜-JšœE˜EJ˜=šžœ žœžœ*ž˜AJšœ,˜,—JšžœB˜FJ˜J˜—š  œžœžœžœ žœ˜FJšœžœžœ˜Jšœ˜Jšœžœžœ˜J˜=šœ˜Jšžœ žœžœžœ ˜K—šžœ žœžœž˜0šžœž˜ Jšžœ žœ!˜0Jšžœ˜ ——šžœ žœž˜šžœ žœžœž˜7JšœM˜M—Jšžœ%˜)—šžœ žœž˜Jšœžœ žœžœ˜B—Jšžœžœ žœ/˜Ešžœžœ2ž˜=šœ˜šœB˜BJšœžœ˜———J˜J˜—š œžœ žœ˜(Jšžœžœ˜/š žœžœžœ=žœžœž˜[Jšœ žœ˜/Jšœ˜J˜Jšžœžœžœžœ˜Jšœžœ/˜<šžœMžœžœž˜aJ™?˜"J˜E—šœ žœžœžœž˜!Jšžœ˜#—Jšžœ žœžœžœ˜šžœ(žœž˜4Jš œ$žœžœ žœžœ˜K—Jšœ˜Jšžœ ˜Jšžœ˜—Jšžœ˜—J˜J˜—š  œžœ˜JšœO˜OJšœžœ˜šœ˜Jš œžœ žœžœžœ ˜M—Jš žœ žœžœ1žœžœ˜QJš œžœžœžœžœ˜DJšœ žœ žœžœ˜Ešžœ žœžœ˜.Jšœžœ-˜@J˜—šžœ˜Jšœ žœ˜Jšœžœ=˜PJšžœ žœ(˜:JšœC˜CJšœnžœžœ˜…Jšœ™J˜—J˜J˜——™J™š  œ˜(Jšœ žœ!˜0Jšœžœ˜Jšœžœ˜Jšœ6˜6Jšžœ žœžœ žœ$˜IJšžœžœžœžœ˜7šžœžœžœžœ˜0J˜KJšžœžœžœ˜)Jš žœžœžœžœžœ˜Dš žœ žœžœžœž˜2Jšœ!˜!—J˜—Jšžœ˜ƒJ˜—J™—™!J™–- -- [reason: UserProfile.ProfileChangeReason]š œ$˜6Jš‘)™)š žœžœžœžœž˜OJšžœ˜—J˜J˜—š œžœžœ˜!Jšœžœ˜&Jšœžœžœ˜šœžœ˜JšœI˜I—šžœžœ˜7JšœI˜I—Jšœ žœžœ˜ šžœžœž˜Jšœžœžœ˜6Jšžœžœ"žœ,˜XJšžœ˜—J˜J˜—š œžœžœ˜#Jšžœžœžœžœ˜%š žœžœžœ=žœžœž˜[J˜"Jšžœ˜—Jšœžœ˜"J˜J˜—š  œžœžœ žœžœ˜MJšœ˜Jšœ žœžœžœ˜,Jšœžœ˜ Jšœžœ%˜=Jšœžœ˜.Jšœžœ˜Jšžœžœžœžœ˜!šœ žœ˜+šžœ˜ Jšžœžœžœžœ ˜O—J™G—Jšžœžœžœžœ˜%š žœžœžœ=žœžœž˜[J˜ šžœBžœžœ˜QJšœ˜Jš žœžœžœžœžœžœ˜>Jšœ˜—Jšžœ˜—šžœžœžœ˜Jšžœžœžœ˜šœ˜JšœJ˜JJšœžœ žœ1˜I—šœ,˜,JšœBžœ˜I—šžœžœž˜)šœT˜TJšœ>žœ˜F——Jš žœ žœžœžœžœžœ˜ZJ˜—Jšžœ;˜?Jšœ'˜'Jšœžœ1˜>šœ'Ÿ2˜YJ™V—šžœMžœžœž˜aJ™SJšœ žœžœžœ$˜BJš žœ žœžœžœžœ˜.˜%JšœKžœ˜R—Jšžœ˜—J˜J˜—–o -- [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL _ FALSE]š  œžœžœžœžœ˜_J˜˜@Jšžœ žœžœžœ˜8—Jšžœžœžœžœ˜,šžœž˜JšœL˜L—J˜J˜—–o -- [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL _ FALSE]š  œžœžœžœžœ˜GJ˜Jšœžœ˜šœ+˜+Jšžœ žœžœžœ˜8—Jšžœ žœžœžœ˜JšœG˜Gšžœž˜Jšœ_˜_—J˜J˜—š œ˜/Jš‘k™kJšœ žœžœžœ˜*J˜#Jš žœžœžœžœžœ˜4š žœžœžœ=žœžœž˜[šžœžœ˜Jšœžœ7˜=Jšžœžœžœ"žœ˜CJšžœ žœžœ'žœ˜VJšžœ˜J˜—J˜ Jšžœ˜—Jšžœžœžœžœ˜0™ZJ™]—Jšœžœ7˜BJšžœžœžœ,˜:J˜J˜—š œ˜.Jš‘k™kJšœ žœžœžœ˜*Jšžœ žœžœ˜˜;J™ψ—J˜J˜—š  œžœžœžœ˜!Jšœžœ˜)Jšœ;žœ˜BJ˜J˜—š œžœ#˜>Jšžœ!˜(Jšœ žœ˜0Jšœ žœž œžœ˜'Jšœžœ˜Jšœ žœ˜%šžœ žœžœ˜Jšœ žœ<˜J—Jšžœžœžœ˜Jšœžœ˜Jšžœžœžœžœ˜J˜#J˜%J˜)J˜&J˜%Jšžœžœžœ˜Jšœžœ%˜/šœ˜JšœQ˜Q—J˜J˜—š œžœžœžœžœžœžœ˜<šœ žœ˜Jšžœžœžœžœžœžœžœžœžœ ˜=—Jšœ#žœžœ ˜EJšžœžœžœ˜#J˜J˜——™J–([proc: UserProfile.ProfileChangedProc]˜J˜=JšœC˜CJšœA˜A—J˜J˜J˜™+K™KK™£Kšœ Οr™—™,™Ο™K™K™K™——Kšœ ’Τœ'’’œ’$™Ξ—K™™-K™7Kšœ ’n™z—™5K™LKš œ ’œ’-œ’ œ’œ™­——…—28P