<> <> <> <> <> 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 = { <<[button: TiogaButton, clientData: REF ANY _ NIL, >> <> 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 = { <<[reason: UserProfile.ProfileChangeReason]>> 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. <<(That's reasonable, since in general a document can be displayed in multiple viewers.)>> 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 = { <<[viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL _ FALSE]>> 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 = { <<[viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL _ FALSE]>> 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 = { <<[viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL _ FALSE]>> 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]; }. <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <>