FinchDirectoryImpl.mesa
Copyright © 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
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;
Types and Typeoids
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.ROPENIL,
where: Where ← office, -- Oh, for closures!
filled: BOOLFALSE
];
Where: TYPE = { office, home };
Selecting, Placing calls
DirectoryButtonNotifier: TiogaButtons.TiogaButtonProc = {
[parent: REF ANY, clientData: REF ANY ← NIL,
mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE]
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] = {
Check these for new Cedar interp.
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: ROPENIL;
instance: DirectoryInstance;
number: ROPENIL;
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
For each node that's a button, fill if necessary, then examine.
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];
This should launch the call
};
};
Manual Directory Access
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]];
};
Creating, Maintaining Directories
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.ROPEIO.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: BOOLTRUE] = {
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 file is unknown, it will be rediscovered later and complained about.
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
For each non-comment node, make it into a button. Maybe later, distinguish levels.
commentProp: REF BOOLNARROW[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;
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.
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];
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.
};
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.ROPENIL] = {
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;
};
Initialization
UserProfile.CallWhenProfileChanges[proc: RebuildDirectories];
Commander.Register["Phone?", PhoneQueryCmd, "Lookup phone number"];
labelledDirectoryIcon ← Icons.NewIconFromFile["Finch.icons", 13];
}.
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)