DIRECTORY Atom USING [ GetProp, PutProp ], Commander USING [ CommandProc, Register ], CommandTool USING [ NextArgument ], FinchSmarts USING [ ConvDesc, ObtainNumbersForRName, PlaceCall ], FinchTool USING [ CheckActive, FeepValue, finchQueue, finchToolHandle, GetSelectedDesc, Report, Status ], FS USING [ ComponentPositions, Error, ExpandName, FileInfo ], Icons USING [ IconFlavor, NewIconFromFile ], IO, MBQueue USING [ QueueClientAction ], Menus USING [ FindEntry, ReplaceMenuEntry ], 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 ], TiogaButtonsExtras USING [ RegisterModifiedProc, StateButton, WasModifiedProc ], TiogaOps USING [ Ref, Root, SetSelection ], UserProfile USING [ CallWhenProfileChanges, ProfileChangedProc, Token ], ViewerClasses USING [ Column, Viewer ], ViewerEvents USING [ EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc ], ViewerOps USING [ AddProp, CloseViewer, DestroyViewer, FetchProp, PaintViewer ], ViewerTools USING [ SetContents ], VoiceUtils USING [ Registrize ] ; FinchDirectoryImpl: CEDAR PROGRAM IMPORTS Atom, Commander, CommandTool, Rope, FinchSmarts, FinchTool, FS, Icons, IO, MBQueue, Menus, NodeProps, TextNode, TiogaButtons, TiogaButtonsExtras, 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 SELECT convDesc.situation.self.state FROM $idle, $inactive => convDesc _ NIL; ENDCASE; convID _ 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=NIL OR 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 ] = { IF finchToolHandle=NIL THEN RETURN; 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.ObtainNumbersForRName[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 BuildDirectory[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; }; BuildDirectory: PUBLIC PROC [directoryFile: ROPE, newOK: BOOL_TRUE] = { root: TextNode.Ref; lastDir: LIST OF ViewerClasses.Viewer _ NIL; msV: ViewerClasses.Viewer _ NIL; directoryIconic: BOOL; baseName: ROPE _ BaseName[directoryFile]; nameWOVersion: ROPE _ NameWithoutVersion[directoryFile]; fullName: ROPE _ FullName[nameWOVersion]; IF fullName=NIL THEN RETURN; 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: DirectoryName[fullName], column: left, menu: NIL, iconic: TRUE, icon: labelledDirectoryIcon, label: baseName] ]; TiogaButtonsExtras.RegisterModifiedProc[v: msV, proc: HandleButtonChange]; Menus.ReplaceMenuEntry[ msV.menu, Menus.FindEntry[msV.menu, "GetImpl"] ]; TiogaButtonsExtras.StateButton[v: msV, show: TRUE]; -- allow editing directory in place ViewerOps.AddProp[msV, $DestroyRegistration, ViewerEvents.RegisterEventProc[HandleViewerDestroy, destroy, msV, TRUE]]; IF finchToolHandle.directories = NIL THEN Atom.PutProp[$FinchRegistrations, $SaveRegistration, ViewerEvents.RegisterEventProc[ proc: HandleDirFileSave, event: save, filter: $Text, before: FALSE]]; [] _ ViewerEvents.RegisterEventProc[ proc: HandleDirViewerSave, event: save, filter: msV, before: FALSE]; IF lastDir#NIL THEN lastDir.rest _ LIST[msV] ELSE finchToolHandle.directories _ LIST[msV]; } ELSE msV.name _ DirectoryName[fullName]; directoryIconic _ msV.iconic; TiogaButtons.LoadViewer[msV, fullName]; IF directoryIconic THEN ViewerOps.CloseViewer[msV]; root _ NARROW[msV.data, TEditDocument.TEditDocumentData].text; NodeProps.PutProp[root, $Viewer, msV]; -- since Button procs don't get you to the viewer. ViewerOps.AddProp[msV, $DirName, nameWOVersion]; -- Hack to keep capitalization right when saving edited directory viewers. Tried other ways, but none worked... BuildButtonsInViewer[root]; }; BuildButtonsInViewer: PROC [root: TextNode.Ref] = { 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; }; FullName: PROC [nameWOVersion: ROPE] RETURNS [fullName: ROPE_NIL] = { IF nameWOVersion=NIL THEN RETURN; fullName _ FS.FileInfo[name: nameWOVersion! FS.Error=> IF error.code=$unknownFile OR error.code=$illegalName THEN CONTINUE].fullFName; }; DirectoryName: PROC [fullName: ROPE] RETURNS [directoryName: ROPE_NIL] = { directoryName _ Rope.Concat["Telephone Directory: ", fullName]; }; NameWithoutVersion: PROC [shortName: ROPE] RETURNS [fullNameWithoutVersion: 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] RETURNS [shortName: 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]; }; HandleDirFileSave: ViewerEvents.EventProc = { IF event#save THEN RETURN; MBQueue.QueueClientAction[finchQueue, AvoidLockup, viewer]; }; AvoidLockup: PROC [r: REF ANY] = { viewer: ViewerClasses.Viewer = NARROW[r]; BuildDirectory[directoryFile: viewer.file, newOK: FALSE]; }; HandleDirViewerSave: ViewerEvents.EventProc = { IF event#save THEN RETURN; MBQueue.QueueClientAction[finchQueue, AvoidLockup2, viewer]; }; AvoidLockup2: PROC [r: REF ANY] = { viewer: ViewerClasses.Viewer = NARROW[r]; nameWOVersion: ROPE _ NameWithoutVersion[viewer.file]; oldName: ROPE _ NARROW[ViewerOps.FetchProp[viewer, $DirName]]; IF Rope.Equal[oldName, nameWOVersion, FALSE] THEN nameWOVersion _ oldName; viewer.name _ DirectoryName[ FullName[nameWOVersion] ]; ViewerOps.PaintViewer[viewer, caption]; }; HandleButtonChange: TiogaButtonsExtras.WasModifiedProc = { root: TextNode.Ref = NARROW[v.data, TEditDocument.TEditDocumentData].text; BuildButtonsInViewer[root]; }; 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_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["PhoneQ", PhoneQueryCmd, "Lookup phone number"]; labelledDirectoryIcon _ Icons.NewIconFromFile["Finch.icons", 12]; }. VFinchDirectoryImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Last Edited by: Swinehart, February 21, 1988 1:36:07 pm PST Last Edited by: Pier, April 26, 1984 3:42:46 pm PST Polle Zellweger (PTZ) March 28, 1989 11:08:14 pm PST Types and Typeoids Selecting, Placing calls [button: TiogaButton, clientData: REF ANY _ NIL, mouseButton: ViewerClasses.MouseButton _ red, shift, control: BOOL _ FALSE] IF shift AND mouseButton = red THEN -- add to callee list for announcements, conferences {}; 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] remove GetImpl button to make room for Edit button with viewers adjusted to 1/2 screen This registers an event to monitor all $Text file saves: if it is a directory file then rebuild the corresponding directory from it. Registers an event to monitor all saves of this viewer: recalculate the viewer name. (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. If file is unknown, it will be rediscovered later and complained about. [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. [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. This is solely to get the capitalization right. It bugged me to have it wrong.... PTZ PROC [v: ViewerClasses.Viewer] 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) Polle Zellweger (PTZ) August 6, 1987 4:27:09 pm PDT reordered icons in Finch.icons changes to: labelledDirectoryIcon Polle Zellweger (PTZ) January 23, 1989 11:27:21 am PST Inserted Doug's code to allow directory viewers to be edited in place (+ one bug fix): new button toggles TiogaButtons buttonhood. If changed when editing finished, rebuild directory. changes to: BuildDirectory, BuildButtonsInViewer, DIRECTORY, FinchDirectoryImpl Polle Zellweger (PTZ) January 31, 1989 11:49:53 am PST Fix the viewer-name-after-save bug correctly this time. changes to: BuildDirectory, BuildButtonsInViewer, DirectoryName, HandleDirFileSave, AvoidLockup, HandleDirViewerSave, AvoidLockup2, HandleButtonChange Polle Zellweger (PTZ) February 1, 1989 3:44:37 pm PST Spreitzer wanted the Edit button to fit in half the screen. changes to: DIRECTORY, BuildDirectory ΚE•NewlineDelimiter – "cedar" style˜™Icodešœ Οmœ1™Jšœžœžœ˜,—Jš œžœ žœžœžœ ˜Tšžœ žœžœž˜0šžœž˜ Jšžœ žœ!˜0Jšžœ˜ ——šžœ žœž˜š žœ žœžœžœžœž˜NJšœM˜M—Jšžœ%˜)—šžœ žœž˜Jšœžœ žœžœ˜B—Jšžœžœ žœ/˜Ešžœžœ2ž˜=šœ˜šœB˜BJšœžœ˜———J˜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˜NJšžœžœžœ˜)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šžœžœ"žœ#˜OJšžœ˜—J˜J˜—šŸœžœžœ˜#Jšžœžœžœžœ˜%š žœžœžœ=žœžœž˜[J˜"Jšžœ˜—Jšœžœ˜"J˜J˜—š Ÿœž œžœ žœžœ˜GJšœ˜Jšœ žœžœžœ˜,Jšœžœ˜ Jšœžœ˜Jšœ žœ˜)Jšœžœ%˜8Jšœ žœ˜)Jšžœ žœžœžœ˜Jšžœžœžœžœ˜%š žœžœžœ=žœžœž˜[J˜ šžœBžœžœ˜QJšœ˜Jš žœžœžœžœžœžœ˜>Jšœ˜—Jšžœ˜—šžœžœžœ˜Jšžœžœžœ˜šœ˜Jšœ:žœ˜>Jšœžœ2˜>—JšœJ˜JšœI˜IJ™V—Jšœ-žœ #˜Xšœ,˜,JšœBžœ˜I—šžœžœž˜)šœT˜Tšœ=žœ˜EJ™„———šœ$˜$šœ=žœ˜DJ™T——šžœ žœžœžœ˜,Jšžœžœ˜-—J˜—Jšžœ$˜(J˜Jšœ'˜'Jšžœžœ˜3Jšœžœ1˜>šœ' 2˜YJ™V—Kšœ2 p˜’Jšœ˜J˜J˜—šŸœžœ˜3šžœMžœžœž˜aJ™SJšœ žœžœžœ$˜BJš žœ žœžœžœžœ˜.šœ%˜%JšœKžœ˜R—Jšžœ˜—J˜J˜—š Ÿœžœžœžœ žœžœ˜EJšžœžœžœžœ˜!šœ žœ˜+šžœ˜ Jšžœžœžœžœ ˜O—J™G—J˜J˜—š Ÿ œžœ žœžœžœžœ˜JJšœ žœ/˜?J˜J˜—–o -- [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL _ FALSE]š Ÿœžœ žœžœžœ˜UJ˜˜@Jšžœ žœžœžœ˜8—Jšžœžœžœžœ˜,šžœž˜JšœL˜L—J˜J˜—–o -- [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL _ FALSE]š Ÿœžœ žœžœ žœ˜=J˜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šœ2žœ˜9J˜J˜—šŸœ˜/Jš‘k™kJšžœ žœžœ˜˜šžœ$žœžœ˜JK™W—Jšœ7˜7Jšœ'˜'J˜J˜—šŸœ(˜:Kšžœ™Jšœžœ/˜JKšœ˜J˜J˜—šŸœžœ#˜>Jšžœ!˜(Jšœ žœ˜0Jšœ žœž œžœ˜'Jšœžœ˜Jšœ žœ˜%šžœ žœžœ˜Jšœ žœ<˜J—Jšžœžœžœ˜Jšœžœ˜Jšžœžœžœžœ˜J˜#J˜%J˜)J˜&J˜%Jšžœžœžœ˜Jšœžœ%˜/šœ˜JšœQ˜Q—J˜J˜—šŸœžœžœžœžœ žœžœ˜7šœ žœ˜Jšžœžœžœžœžœžœžœžœžœ ˜=—Jšœ#žœžœ ˜EJšžœžœžœ˜#J˜—J˜—™J–([proc: UserProfile.ProfileChangedProc]˜J˜=JšœC˜CJšœA˜A—J˜J˜™+K™KK™£Kšœ Οr™—™,™Ο™K™K™K™——Kšœ £Τœ'£’œ£$™Ξ—™-K™7Kšœ £n™z—™5K™LKš œ £œ£-œ£ œ£œ™­—™3K™Kšœ £™!—™6K™ΈKšœ £C™O—™6K™7Kšœ £Š™–—™5K™;Kšœ £™%——…—9ja