<> <> <> <> <> DIRECTORY Atom USING [ GetProp, PutProp ], FinchSmarts USING [ ConvDesc, 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 ], 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, 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 = { <<[parent: REF ANY, clientData: REF ANY _ NIL, >> <> button: TiogaButtons.TiogaButton _ NARROW[parent]; 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]; }; CallByDescription: PUBLIC PROC[description: ROPE, residence: BOOL] = { rName: ROPE_NIL; instance: DirectoryInstance; number: ROPE_NIL; 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 FOR dL: LIST OF ViewerClasses.Viewer _ finchToolHandle.directories, dL.rest WHILE dL#NIL DO instance _ FindNamedInstance[dL.first, rName]; IF instance#NIL THEN EXIT; ENDLOOP; IF instance # NIL THEN CallNumber[instance, IF residence THEN home ELSE office] ELSE IF residence THEN FinchTool.Report["Residence number not found"] ELSE IF FinchTool.CheckActive[FinchTool.finchToolHandle] THEN FinchSmarts.PlaceCall[rName: rName, number: FinchTool.FeepValue[number]]; }; FindNamedInstance: PROC[v: Viewer, name: Rope.ROPE] RETURNS [ instance: DirectoryInstance_NIL ] = { rName: Rope.ROPE = VoiceUtils.Registrize[name]; root: TextNode.Ref; IF v=NIL THEN RETURN; 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: DirectoryInstance = 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 LOOP; SelectEntryInDirectory[button]; RETURN[instance]; ENDLOOP; }; CallNumber: PROC[instance: DirectoryInstance, where: Where] = { name, number: Rope.ROPE; 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[rName: name, number: FinchTool.FeepValue[number], useNumber: where=home OR instance.rName=NIL]; <> }; }; <> <<>> 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 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. <<(That's reasonable, since in general a document can be displayed in multiple viewers.)>> 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 = { <<[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]; }; HandleViewerChange: ViewerEvents.EventProc = { <<[viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL _ FALSE]>> 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]; labelledDirectoryIcon _ Icons.NewIconFromFile["Finch.icons", 13]; }. <> <> <> <> <> <> <> <> <> <> <> <<>> <> <> <> <> <> <>