DIRECTORY Atom USING [ GetProp, PutProp ], Commander USING [ CommandProc, Register ], CommanderOps 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, TiogaButton, TiogaButtonProc, 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, CommanderOps, Rope, FinchSmarts, FinchTool, FS, Icons, IO, MBQueue, Menus, 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[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, [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.PutFR1["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 ¬ CommanderOps.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, \n", rope[instance.name], rope[instance.rName], rope[instance.number]]; IO.PutF1[cmd.out, "%g\n", 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] ]; TiogaButtons.RegisterModifiedProc[v: msV, proc: HandleButtonChange]; Menus.ReplaceMenuEntry[ msV.menu, Menus.FindEntry[msV.menu, "GetImpl"] ]; TiogaButtons.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: 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: TiogaButtons.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 Σ 1985, 1992 by Xerox Corporation. All rights reserved. Last Edited by: Swinehart, June 4, 1992 10:41 pm PDT 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 Κ!–(cedarcode) style•NewlineDelimiter ˜™Icodešœ Οeœ6™BJ™4J™3K™4—K˜šΟk ˜ K˜ Kšœ žœ˜*Kšœ žœ˜$Kšœ žœ0˜AKšœ žœZ˜iKšžœžœ5˜=Kšœžœ!˜,Kšžœ˜Kšœžœ˜$Kšœžœ!˜,Kšœ žœ˜%K˜Kšœžœ˜*Kšœ žœ˜$Kšœžœ ˜,Kšœ žœ ˜²Kšœ žœ˜+Kšœ žœ7˜HKšœžœ˜'Kšœ žœJ˜\Kšœ žœA˜PKšœ žœ˜"Kšœ žœ˜K˜—K˜šΟnœžœž˜!Kšžœ>žœ žœ|˜ΞKšžœ˜Kšžœ žœ˜K˜—™J™Kšžœžœžœ˜Kšœžœ˜$K˜K˜/K˜Kšœžœžœ˜4šœžœžœ˜&Kšœ žœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kšœžœž˜Kšœžœžœ˜KšœΟc˜+Kšœžœž˜K˜—K˜Kšœžœ˜K˜—™J™•StartOfExpansion‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]šŸœ"˜9JšΠckΟs‘™1Jš‘ ’ ‘1™KK˜&šžœžœžœ 4™YK™—Kšœ˜šœžœžœžœž˜3Kšœžœžœžœ˜U—Kšžœ žœžœžœ˜,Kšœžœ žœ žœ ˜NKšœ?˜?K˜K˜—šŸœžœ&˜BK˜9šœ˜Kšžœ#˜)—Kšžœžœžœžœ˜˜K˜,K˜AK˜—K˜K˜—šŸœžœžœžœ˜%J™!Kšœ#žœ˜-K˜EK˜=šžœ žœžœ*ž˜AKšœ,˜,—KšžœB˜FK˜K˜—š Ÿœžœžœžœ žœ˜FKšœžœžœ˜Kšœ˜Kšœžœžœ˜K˜=Kšœ˜š žœ žœžœžœž˜>Kšœžœžœ˜,—Kš œžœ žœžœžœ ˜Tšžœ žœžœž˜0šžœž˜ Kšžœ žœ!˜0Kšžœ˜ ——šžœ žœž˜š žœ žœžœžœžœž˜NKšœM˜M—Kšžœ%˜)—šžœ žœž˜Kšœžœ žœžœ˜B—Kšžœžœ žœ/˜Ešžœžœ2ž˜=šœ˜šœB˜BKšœžœ˜———K˜K˜—šŸœžœ žœ˜(Kšžœžœ˜/Kšžœžœžœžœ˜#š žœžœžœ=žœžœž˜[Kšœ žœ˜/Kšœ˜K˜Kšžœžœžœžœ˜Kšœžœ/˜<šžœMžœžœž˜aJ™?˜"K˜+—šœ žœžœžœž˜!Kšžœ˜#—Kšžœ žœžœžœ˜šžœ(žœž˜4Kš œ$žœžœ žœžœ˜K—Kšœ˜Kšžœ ˜Kšžœ˜—Kšžœ˜—K˜K˜—šŸ œžœ˜KšœO˜OKšœžœ˜šœ˜Kš œžœ žœžœžœ ˜M—Kš žœ žœžœ1žœžœ˜QKš œžœžœžœžœ˜DKšœ žœ žœžœ˜Ešžœ žœžœ˜.Kšœžœ.˜AK˜—šžœ˜Kšœ žœ˜Kšœžœ=˜PKšžœ žœ(˜:KšœC˜CKšœnžœžœ˜…Jšœ™K˜—K˜—K˜—™J™šŸ œ˜(Kšœ žœ"˜1Kšœžœ˜Kšœžœ˜K˜6Kšžœ žœžœ žœ$˜IKšžœžœžœžœ˜7šžœžœžœžœ˜0K˜NKšžœžœžœ˜)Kš žœžœžœžœžœ˜Dš žœ žœžœžœž˜2K˜!—K˜—Kšžœd˜fKšžœ3˜5K˜—J™—™!J™–- -- [reason: UserProfile.ProfileChangeReason]šŸœ$˜6Jš‘)™)š žœžœžœžœž˜OKšžœ˜—K˜K˜—šŸœžœžœ˜!Kšœžœ˜&Kšœžœžœ˜šœžœ˜KšœI˜I—šžœžœ˜7KšœI˜I—Kšœ žœžœ˜ šžœžœž˜Kšœžœžœ˜6Kšžœžœ"žœ#˜OKšžœ˜—K˜K˜—šŸœžœžœ˜#Kšžœžœžœžœ˜%š žœžœžœ=žœžœž˜[K˜"Kšžœ˜—Kšœžœ˜"K˜K˜—š Ÿœž œžœ žœžœ˜GKšœ˜Kšœ žœžœžœ˜,Kšœžœ˜ Kšœžœ˜Kšœ žœ˜)Kšœžœ%˜8Kšœ žœ˜)Kšžœ žœžœžœ˜Kšžœžœžœžœ˜%š žœžœžœ=žœžœž˜[K˜ šžœBžœžœ˜QK˜Kš žœžœžœžœžœžœ˜>Kšœ˜—Kšžœ˜—šžœžœžœ˜Kšžœžœžœ˜˜Kšœ:žœ˜>Kšœžœ2˜>—K˜DšœI˜IJ™V—Kšœ'žœ #˜Ršœ,˜,KšœBžœ˜I—šžœžœž˜)šœT˜Tšœ=žœ˜EJ™„———˜$šœ=žœ˜DJ™T——šžœ žœžœžœ˜,Kšžœžœ˜-—K˜—Kšžœ$˜(K˜Kšœ'˜'Kšžœžœ˜3Kšœžœ1˜>šœ' 2˜YJ™V—Kšœ2 p˜’Kšœ˜K˜K˜—šŸœžœ˜3šžœMžœžœž˜aJ™SKšœ žœžœžœ$˜BKš žœ žœžœžœžœ˜.˜%Kšœ1žœ˜8—Kšžœ˜—K˜K˜—š Ÿœžœžœžœ žœžœ˜EKšžœžœžœžœ˜!šœ žœ˜+šžœ˜ Kšžœžœžœžœ ˜O—J™G—K˜K˜—š Ÿ œžœ žœžœžœžœ˜JKšœ žœžœ/˜?K˜K˜—–o -- [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL _ FALSE]š Ÿœžœ žœžœžœ˜UK˜˜@Kšžœ žœžœžœ˜8—Kšžœžœžœžœ˜,šžœž˜K˜L—K˜K˜—–o -- [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL _ FALSE]š Ÿœžœ žœžœ žœ˜=K˜Kšœžœ˜˜+Kšžœ žœžœžœ˜8—Kšžœ žœžœžœ˜K˜Gšžœž˜K˜_—K˜K˜—šŸœ˜/Jš‘k™kKšœ žœžœžœ˜*K˜#Kš žœžœžœžœžœ˜4š žœžœžœ=žœžœž˜[šžœžœ˜Kšœžœ7˜=Kšžœžœžœ"žœ˜CKšžœ žœžœ'žœ˜VKšžœ˜K˜—K˜ Kšžœ˜—Kšžœžœžœžœ˜0™ZJ™]—Kšœžœ7˜BKšžœžœžœ,˜:K˜K˜—šŸœ˜-Jš‘k™kKšžœ žœžœ˜˜;J™ψ—K˜K˜—šŸ œžœžœžœ˜"Kšœžœ˜)Kšœ2žœ˜9K˜K˜—šŸœ˜/Jš‘k™kKšžœ žœžœ˜˜šžœ$žœžœ˜JK™W—K˜7Kšœ'˜'K˜K˜—šŸœ"˜4Kšžœ™Kšœžœ/˜JKšœ˜K˜K˜—šŸœžœ#˜>Kšžœ!˜(Kšœ žœ˜0Kšœ žœž œžœ˜'Kšœžœ˜Kšœ žœ˜%šžœ žœžœ˜Kšœ žœ<˜J—Kšžœžœžœ˜Kšœžœ˜Kšžœžœžœžœ˜K˜#K˜%K˜)K˜&K˜%Kšžœžœžœ˜Kšœžœ&˜/˜KšœQ˜Q—K˜K˜—šŸœžœžœžœžœ žœžœ˜7šœ žœ˜Kšžœžœžœžœžœžœžœžœžœ ˜=—Kšœ#žœžœ ˜EKšžœžœžœ˜#K˜—K˜—™K–([proc: UserProfile.ProfileChangedProc]˜K˜=KšœC˜CK˜A—K˜K˜™+K™KK™£Kšœ Οr™—™,™Ο™K™K™K™——Kšœ £Τœ'£’œ£$™Ξ—™-K™7Kšœ £n™z—™5K™LKš œ £œ£-œ£ œ£œ™­—™3K™Kšœ £™!—™6K™ΈKšœ £C™O—™6K™7Kšœ £Š™–—™5K™;Kšœ £™%——…—8ά`S