FinchDirectoryImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by: Swinehart, February 21, 1988 1:36:07 pm PST
Last Edited by: Pier, April 26, 1984 3:42:46 pm PST
Polle Zellweger (PTZ) March 28, 1989 11:08:14 pm PST
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;
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 = {
[button: TiogaButton, clientData: REF ANY ← NIL,
mouseButton: ViewerClasses.MouseButton ← red, shift, control: BOOL ← FALSE]
directoryInstance: DirectoryInstance;
IF shift AND mouseButton = red THEN -- add to callee list for announcements, conferences
{};
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 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
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.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]];
};
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 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: BOOLTRUE] = {
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"] ];
remove GetImpl button to make room for Edit button with viewers adjusted to 1/2 screen
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]];
This registers an event to monitor all $Text file saves: if it is a directory file then rebuild the corresponding directory from it.
[] ← ViewerEvents.RegisterEventProc[
proc: HandleDirViewerSave, event: save, filter: msV, before: FALSE];
Registers an event to monitor all saves of this viewer: recalculate the viewer name.
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
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;
};
FullName: PROC [nameWOVersion: ROPE] RETURNS [fullName: ROPENIL] = {
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.
};
DirectoryName: PROC [fullName: ROPE] RETURNS [directoryName: ROPENIL] = {
directoryNameRope.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;
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];
};
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];
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];
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];
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.
};
AvoidLockup2: PROC [r: REF ANY] = {
viewer: ViewerClasses.Viewer = NARROW[r];
nameWOVersion: ROPE ← NameWithoutVersion[viewer.file];
oldName: ROPENARROW[ViewerOps.FetchProp[viewer, $DirName]];
IF Rope.Equal[oldName, nameWOVersion, FALSE] THEN nameWOVersion ← oldName;
This is solely to get the capitalization right. It bugged me to have it wrong.... PTZ
viewer.name ← DirectoryName[ FullName[nameWOVersion] ];
ViewerOps.PaintViewer[viewer, caption];
};
HandleButtonChange: TiogaButtonsExtras.WasModifiedProc = {
PROC [v: ViewerClasses.Viewer]
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: 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["PhoneQ", PhoneQueryCmd, "Lookup phone number"];
labelledDirectoryIcon ← Icons.NewIconFromFile["Finch.icons", 12];
}.
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