FinchDirectoryImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by: Swinehart, November 12, 1986 4:57:28 pm PST
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.ROPE ← NIL,
where: Where ← office, -- Oh, for closures!
filled: BOOL←FALSE
];
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]
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] = {
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: ROPE←NIL;
instance: DirectoryInstance;
number: ROPE←NIL;
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.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 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 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;
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.
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;
};
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)