FingerTool.mesa
Last Edited by: Khall, August 17, 1984 11:21:52 am PDT
Last edited by: Donahue, October 22, 1984 10:02:48 am PDT
DIRECTORY
AIS,
BasicTime USING [nullGMT, GMT, Period],
Buttons USING [Button, ButtonProc, Create, ReLabel, SetDisplayStyle],
Commander USING [CommandProc, Handle, Register, CommandObject],
CommandTool USING [DoCommandRope],
Containers USING [Container, Create],
Convert USING [RopeFromCard, RopeFromTime],
DefaultRegistry USING[UsersRegistry],
FingerOps,
FS USING [Error],
GalleryOps USING [Create, CreateGallery, Gallery, SetAis, Vp],
Graphics,
GraphicsOps,
Icons USING [IconFlavor, NewIconFromFile],
IO USING[noWhereStream],
MessageWindow USING [Append, Blink],
Process USING[Detach],
Rope USING [Cat, Equal, Length, ROPE],
UserCredentials USING [Get],
VFonts,
ViewerClasses,
ViewerEvents,
ViewerOps,
ViewerTools;
FingerTool: CEDAR PROGRAM
IMPORTS
AIS, BasicTime, Buttons, Commander, CommandTool, Containers, Convert, DefaultRegistry, FingerOps, FS, Graphics, GraphicsOps, Icons, IO, MessageWindow, Process, Rope, UserCredentials, ViewerEvents, VFonts, ViewerOps, ViewerTools =
BEGIN
FingerHandle: TYPE = REF FingerToolRec; -- a REF to the data for a particular instance of the finger tool; multiple instances can be created.
FingerToolRec: TYPE =
RECORD -- the data for a particular tool instance
[outer: Containers.Container ← NIL, -- handle for the enclosing container
height: CARDINAL ← 0, -- height measured from the top of the container
performFingerButton: Buttons.Button, -- button clicked to perform finger operation
storeChangesButton: Buttons.Button, -- button clicked to make changes to finger data
talkButton: Buttons.Button, -- button to connect to talk program
fingerList: FingerOps.FingerList, -- list of finger data
remainingFingerEntries: CARDINAL ← 0, -- number of objects remaining to be displayed
fingerObject: FingerOps.FingerObject, -- the current object being displayed (name = NIL if nothing being displayed currently)
the following hold all the data pertinent to selections for each fingered object
name,
actualName,
pictureFileName,
plan: SelectionData ← NEW[SelectionRecord],
opt1, opt2, opt3: SelectionData ← NEW[SelectionRecord],
these three entries change meaning depending on the type of the object being displayed. For machines they give the last user interaction, who the user was and the time of the interaction; for people, they give the last time mail was read, the last machine logged in to and the last login time
editable: BOOLFALSE, -- if the current entry is the logged-in user or this machine, then the fields of the entry can be changed
pictureAISFile: Rope.ROPE,
pictureGallery: GalleryOps.Gallery, -- for when Gallery is released
pictureViewPort: GalleryOps.Vp,
pictureViewer: ViewerClasses.Viewer];
SelectionData: TYPE = REF SelectionRecord;
SelectionRecord: TYPE =
RECORD
[parentHandle: FingerHandle,
selected: BOOL,
selectable: BOOL,
button: Buttons.Button,
result: ViewerClasses.Viewer,
registration: ViewerEvents.EventRegistration ← NIL];
PictureState: TYPE = REF PictureStateRecord;
PictureStateRecord: TYPE = RECORD[width, height: INT, filename: Rope.ROPE];
fingerIcon: Icons.IconFlavor = Icons.NewIconFromFile["Finger.icon", 0];
MakeFingerTool: Commander.CommandProc =
BEGIN
fingerHandle: FingerHandle = NEW[FingerToolRec ← [fingerObject: [type: person, name: NIL]]];
fingerHandle.outer ← Containers.Create -- construct the outer container
[[name: "Finger Tool", -- name displayed in the caption
icon: fingerIcon,
iconic: TRUE, -- so tool will be iconic when first created
column: left, -- initially in the left column
scrollable: FALSE]]; -- inhibit user from scrolling contents
fingerHandle.outer.class.bltContents ← none;
fingerHandle.height ← 0;
fingerHandle.remainingFingerEntries ← 0;
SetupViewer[fingerHandle];
ViewerOps.SetOpenHeight[fingerHandle.outer, fingerHandle.height]; -- hint our desired height
ViewerOps.PaintViewer[fingerHandle.outer, all]; -- reflect above change
END;
For uniformity, some standard distances between entries in the tool are defined.
pictureColumnWidth: CARDINAL = 192;
pictureHeight: CARDINAL = 192;
entryVSpace: CARDINAL = 8; -- vertical leading space between sections
entryHeight: CARDINAL = 15; -- how tall to make each line of items
horizSpace: CARDINAL = 10; -- space between items on the same line
firstColumnIndent: CARDINAL = 1; -- measured from left margin
firstColumnWidth: CARDINAL = 110;
secondColumnWidth: CARDINAL = 240;
pictureFont: Graphics.FontRef =
VFonts.GraphicsFont[VFonts.EstablishFont["Helvetica", 12, TRUE]];
SetupViewer: PROC [fingerHandle: FingerHandle] =
BEGIN
SetupButtons: PROC [name: Rope.ROPE, which: SelectionData, scroll: BOOLFALSE, height: CARDINAL ← entryHeight, edit: BOOLFALSE] =
BEGIN
which.button ← Buttons.Create
[info:
[name: name,
wx: firstColumnIndent, -- keep the buttons aligned
wy: fingerHandle.height,
ww: firstColumnWidth,
wh: entryHeight, -- specify rather than defaulting so line is uniform
parent: fingerHandle.outer,
border: FALSE ],
proc: SelectionButtonClicked,
clientData: which]; -- this will be passed to our button proc
which.parentHandle ← fingerHandle;
which.selected ← NOT Rope.Equal[name, "Picture Filename:"]; -- the default is not to set the Picture file as selected so that we don't fill up the FS cache with AIS files!
which.selectable ← Rope.Equal[name, "Plan:"] OR Rope.Equal[name, "Picture Filename:"];
Buttons.SetDisplayStyle[which.button, IF which.selected THEN $WhiteOnBlack ELSE $BlackOnWhite];
which.result ← ViewerOps.CreateViewer
[flavor: $Text,
info:
[wx: firstColumnIndent + firstColumnWidth + horizSpace, -- keep the buttons aligned
wy: fingerHandle.height,
ww: secondColumnWidth,
wh: height,
scrollable: scroll,
parent: fingerHandle.outer,
border: FALSE]];
IF NOT edit THEN ViewerTools.InhibitUserEdits[which.result];
fingerHandle.height ← fingerHandle.height + height; -- maintain total height
END;
fingerHandle.height ← fingerHandle.height + entryVSpace; -- space down from the top of the viewer
fingerHandle.performFingerButton ← Buttons.Create -- button for performing finger
[info:
[name: "perform finger",
wx: 0,
wy: fingerHandle.height,
wh: entryHeight, -- specify rather than defaulting so line is uniform
parent: fingerHandle.outer,
border: TRUE],
clientData: fingerHandle, -- this will be passed to our button proc
proc: PerformFingerAtTool];
fingerHandle.storeChangesButton ← Buttons.Create -- button for making changes to finger data
[info:
[name: "store changes",
wx: fingerHandle.performFingerButton.wx + fingerHandle.performFingerButton.ww + horizSpace,
wy: fingerHandle.height,
wh: entryHeight, -- specify rather than defaulting so line is uniform
parent: fingerHandle.outer,
border: TRUE],
clientData: fingerHandle, -- this will be passed to our button proc
proc: StoreChanges];
fingerHandle.talkButton ← Buttons.Create -- button to connect to talk program
[info:
[name: "talk to",
wx: fingerHandle.storeChangesButton.wx + fingerHandle.storeChangesButton.ww + horizSpace,
wy: fingerHandle.height,
wh: entryHeight, -- specify rather than defaulting so line is uniform
parent: fingerHandle.outer,
border: TRUE],
clientData: fingerHandle, -- this will be passed to our button proc
proc: TalkTo];
fingerHandle.height ← fingerHandle.height + entryHeight + entryVSpace; -- space down from the action buttons
SetupButtons[name: "Grapevine ID:", which: fingerHandle.name];
SetupButtons[name: "User Name:", which: fingerHandle.actualName, edit: TRUE];
fingerHandle.actualName.registration ← ViewerEvents.RegisterEventProc[SetNew, edit, fingerHandle.actualName.result];
SetupButtons[name: "Last Login On:", which: fingerHandle.opt1];
SetupButtons[name: "Last Login At:", which: fingerHandle.opt2];
SetupButtons[name: "Last Mail Read:", which: fingerHandle.opt3];
SetupButtons[name: "Picture Filename:", which: fingerHandle.pictureFileName, edit: TRUE];
fingerHandle.pictureFileName.registration ← ViewerEvents.RegisterEventProc[SetNew, edit, fingerHandle.pictureFileName.result];
SetupButtons[name: "Plan:", which: fingerHandle.plan, scroll: TRUE, height: 8*entryHeight, edit: TRUE];
fingerHandle.plan.registration ← ViewerEvents.RegisterEventProc[SetNew, edit, fingerHandle.plan.result];
fingerHandle.pictureGallery ← GalleryOps.CreateGallery -- frame for picture
[viewerRec:
[wx: firstColumnIndent + firstColumnWidth + horizSpace + secondColumnWidth + horizSpace,
wy: fingerHandle.userName.result.wy,
ww: pictureColumnWidth,
wh: pictureHeight,
parent: fingerHandle.outer,
border: TRUE],
oneVpOnly: TRUE];
fingerHandle.pictureViewPort ← GalleryOps.Create
[gallery: fingerHandle.pictureGallery,
sx: pictureColumnWidth,
sy: pictureHeight,
fixedVp: TRUE,
fixedAIS: TRUE,
paint: FALSE];
fingerHandle.pictureViewer ← ViewerOps.CreateViewer[flavor: $FingerPicture,
info: [wx: firstColumnIndent + firstColumnWidth + horizSpace + secondColumnWidth + horizSpace, wy: fingerHandle.name.result.wy, ww: pictureColumnWidth, wh: pictureHeight, parent: fingerHandle.outer, border: TRUE, scrollable: FALSE],
paint: TRUE];
fingerHandle.height ← fingerHandle.height + entryVSpace; -- space at bottom of finger viewer
END;
SetNew: ViewerEvents.EventProc = {
WHILE viewer.parent # NIL DO viewer ← viewer.parent ENDLOOP;
viewer.newVersion ← TRUE;
TRUSTED { Process.Detach[FORK ViewerOps.PaintViewer[viewer: viewer, hint: caption]] }
};
PaintPicture: ViewerClasses.PaintProc = {
state: PictureState = NARROW[self.data];
box:Graphics.Box = Graphics.GetBounds[context];
IF state = NIL THEN {
[] ← Graphics.SetPaintMode[self:context, mode:opaque];
Graphics.SetColor[context, Graphics.white];
Graphics.DrawBox[context, box];
Graphics.SetColor[context, Graphics.black];
Graphics.SetCP[context, 3.0, pictureHeight/2];
Graphics.DrawRope[self: context, rope: "No Picture", font: pictureFont];
RETURN
};
BEGIN
scaleX:REAL ← (box.xmax-box.xmin)/ state.width;
scaleY:REAL ← (box.ymax-box.ymin)/ state.height;
scale: REALMIN[scaleX, scaleY];
Graphics.SetCP[self:context, x:0, y:0];
Graphics.Scale[self:context, sx:scale, sy:scale];
[] ← Graphics.SetPaintMode[self:context, mode:opaque];
Graphics.SetColor[self:context, color:Graphics.black];
Graphics.DrawImage[self:context, image:GraphicsOps.NewAisImage[state.filename]];
END };
fingerErrorCode: FingerOps.Reason;
PerformFingerAtTool: Buttons.ButtonProc =
BEGIN
ENABLE
FingerOps.FingerError => {fingerErrorCode ← reason; GOTO FingerProblem };
fingerHandle: FingerHandle ← NARROW[clientData];
user: Rope.ROPE = UserCredentials.Get[].name;
actualName, plan, pictureFile: Rope.ROPE;
data: FingerOps.ObjectData;
IF fingerHandle.remainingFingerEntries = 0 THEN {
userNamePattern: Rope.ROPE ← ViewerTools.GetSelectionContents[];
IF Rope.Length[userNamePattern] = 0 THEN userNamePattern ← "*";
fingerHandle.fingerList ← FingerOps.GetMatchingObjects[pattern: userNamePattern];
fingerHandle.remainingFingerEntries ← 0;
FOR list: FingerOps.FingerList ← fingerHandle.fingerList, list.rest UNTIL list = NIL DO
fingerHandle.remainingFingerEntries ← fingerHandle.remainingFingerEntries + 1
ENDLOOP };
IF fingerHandle.remainingFingerEntries > 0 THEN
BEGIN
entry: FingerOps.FingerObject = fingerHandle.fingerList.first;
If there are entries remaining, treat the blue mouse button as "cancel"
IF mouseButton = blue THEN {
fingerHandle.fingerList ← NIL;
fingerHandle.remainingFingerEntries ← 0;
Buttons.ReLabel[fingerHandle.performFingerButton, "perform finger"];
RETURN };
[actualName, plan, pictureFile, data] ← FingerOps.GetProps[entry];
fingerHandle.editable ← IF entry.type = person THEN Rope.Equal[entry.name, user, FALSE] OR Rope.Equal[Rope.Cat[entry.name, DefaultRegistry.UsersRegistry[]], user, FALSE] ELSE Rope.Equal[entry.name, FingerOps.machineNameRope, FALSE];
The user can change the plan and picture file for himself or the machine he is logged onto; he can also change his own "actual name"
fingerHandle.fingerObject ← entry;
fingerHandle.fingerList ← fingerHandle.fingerList.rest;
fingerHandle.remainingFingerEntries ← fingerHandle.remainingFingerEntries - 1;
SetName[fingerHandle, entry, actualName];
IF entry.type = machine THEN SetMachineFields[fingerHandle, entry.name, data]
ELSE SetPersonFields[fingerHandle, entry.name, data];
ViewerTools.SetContents[fingerHandle.pictureFileName.result, fingerHandle.pictureAISFile ← IF fingerHandle.pictureFileName.selected AND pictureFile # NIL AND NOT Rope.Equal[pictureFile, ""] THEN pictureFile ELSE NIL];
IF fingerHandle.editable AND fingerHandle.pictureFileName.selected THEN
ViewerTools.EnableUserEdits[fingerHandle.pictureFileName.result]
ELSE ViewerTools.InhibitUserEdits[fingerHandle.pictureFileName.result];
GalleryOps.SetAis[fingerHandle.pictureViewPort, fingerHandle.pictureAISFile ! FS.Error => CONTINUE];
IF fingerHandle.pictureAISFile # NIL THEN
SetPicture[fingerHandle.pictureViewer, fingerHandle.pictureAISFile]
ELSE fingerHandle.pictureViewer.data ← NIL;
ViewerOps.PaintViewer[fingerHandle.pictureViewer, all];
ViewerTools.SetContents[fingerHandle.plan.result, IF fingerHandle.plan.selected THEN plan ELSE ""];
IF fingerHandle.editable AND fingerHandle.plan.selected THEN
ViewerTools.EnableUserEdits[fingerHandle.plan.result]
ELSE ViewerTools.InhibitUserEdits[fingerHandle.plan.result];
fingerHandle.outer.newVersion ← FALSE;
ViewerOps.PaintViewer[fingerHandle.outer, caption]
END;
IF fingerHandle.remainingFingerEntries > 0
THEN
Buttons.ReLabel[fingerHandle.performFingerButton, Rope.Cat["remaining: ", Convert.RopeFromCard[fingerHandle.remainingFingerEntries]]]
ELSE
Buttons.ReLabel[fingerHandle.performFingerButton, "perform finger"]
EXITS FingerProblem => {
MessageWindow.Append[
message: SELECT fingerErrorCode FROM
Aborted => "\n... Transaction Aborted; Retry Finger Operation",
Error => "\n... Internal Finger Error; Contact Implementor",
Failure => "\n... Connection with server broken; try again later",
ENDCASE => NIL,
clearFirst: TRUE ];
MessageWindow.Blink[] };
END;
SetName: PROC[handle: FingerHandle, object: FingerOps.FingerObject, actualName: Rope.ROPE] ~ {
IF object.type = person THEN {
Buttons.ReLabel[handle.name.button, "Grapevine ID:"];
ViewerTools.SetContents[handle.name.result, object.name];
Buttons.ReLabel[handle.actualName.button, "User Name:"];
ViewerTools.SetContents[handle.actualName.result, actualName];
IF handle.editable THEN ViewerTools.EnableUserEdits[handle.actualName.result]
ELSE ViewerTools.InhibitUserEdits[handle.actualName.result] }
ELSE {
Buttons.ReLabel[handle.name.button, "Machine Name:"];
ViewerTools.SetContents[handle.name.result, object.name];
Buttons.ReLabel[handle.actualName.button, "Net Number:"];
ViewerTools.SetContents[handle.actualName.result, actualName];
ViewerTools.InhibitUserEdits[handle.actualName.result] } };
SetMachineFields: PROC[handle: FingerHandle, name: Rope.ROPE, data: FingerOps.ObjectData] ~ TRUSTED {
WITH machineData: data^ SELECT FROM
machine => {
Buttons.ReLabel[handle.opt1.button, "Last User:"];
ViewerTools.SetContents[handle.opt1.result, machineData.lastUser];
Buttons.ReLabel[handle.opt2.button, "Interaction Time:"];
ViewerTools.SetContents[handle.opt2.result, TimeRope[machineData.time]];
Buttons.ReLabel[handle.opt3.button, "Last Interaction:"];
ViewerTools.SetContents[handle.opt3.result, IF machineData.operation = login THEN "Login" ELSE "Logout"] };
ENDCASE };
TimeRope: PROC[time: BasicTime.GMT] RETURNS[rope: Rope.ROPE] = {
rope ← IF time = BasicTime.nullGMT THEN "unknown"
ELSE Convert.RopeFromTime[time] };
SetPersonFields: PROC[handle: FingerHandle, name: Rope.ROPE, data: FingerOps.ObjectData] ~ TRUSTED {
lastLoginTime: BasicTime.GMT ← BasicTime.nullGMT;
lastLoginMachine: Rope.ROPE;
WITH personData: data^ SELECT FROM
person => {
Buttons.ReLabel[handle.opt1.button, "Last Mail Read:"];
ViewerTools.SetContents[handle.opt1.result, TimeRope[personData.mailRead]];
FOR mList: LIST OF FingerOps.MachineData ← personData.used, mList.rest UNTIL mList = NIL DO
IF mList.first.operation = login AND (lastLoginTime = BasicTime.nullGMT OR BasicTime.Period[from: lastLoginTime, to: mList.first.time] > 0) THEN {
lastLoginTime ← mList.first.time; lastLoginMachine ← mList.first.machine }
ENDLOOP;
Buttons.ReLabel[handle.opt2.button, "Last Login On:"];
ViewerTools.SetContents[handle.opt2.result, lastLoginMachine];
Buttons.ReLabel[handle.opt3.button, "Last Login At:"];
ViewerTools.SetContents[handle.opt3.result, TimeRope[lastLoginTime]] };
ENDCASE };
SetPicture: PROC[v: ViewerClasses.Viewer, filename: Rope.ROPE] ~ {
ENABLE FS.Error => CONTINUE;
fd:AIS.FRef = AIS.OpenFile[name:filename];
r:AIS.Raster = AIS.ReadRaster[fd];
AIS.CloseFile[fd];
v.data ← NEW[PictureStateRecord ← [filename: filename, height: r.scanCount, width: r.scanLength]] };
StoreChanges: Buttons.ButtonProc =
BEGIN
ENABLE
FingerOps.FingerError => {fingerErrorCode ← reason; GOTO FingerProblem };
fingerHandle: FingerHandle = NARROW[clientData]; -- get finger viewer data
name: Rope.ROPE = fingerHandle.fingerObject.name;
IF Rope.Length[name] # 0 THEN
BEGIN
IF fingerHandle.editable THEN
BEGIN
actualName: Rope.ROPE = IF fingerHandle.actualName.result.newVersion THEN ViewerTools.GetContents[fingerHandle.actualName.result] ELSE NIL;
plan: Rope.ROPE = IF fingerHandle.plan.result.newVersion THEN ViewerTools.GetContents[fingerHandle.plan.result] ELSE NIL;
pictureFileName: Rope.ROPE = IF fingerHandle.pictureFileName.result.newVersion THEN ViewerTools.GetContents[fingerHandle.pictureFileName.result] ELSE NIL;
fingerHandle.actualName.result.newVersion ← fingerHandle.plan.result.newVersion ← fingerHandle.pictureFileName.result.newVersion ← FALSE;
FingerOps.SaveNewProps[object: fingerHandle.fingerObject, actualName: actualName, plan: plan, pictureFile: pictureFileName];
fingerHandle.remainingFingerEntries ← 0;
fingerHandle.fingerList ← NIL;
Buttons.ReLabel[fingerHandle.performFingerButton, "perform finger"];
IF Rope.Length[pictureFileName] # 0 THEN {
fingerHandle.pictureAISFile ← pictureFileName;
SetPicture[fingerHandle.pictureViewer, fingerHandle.pictureAISFile];
ViewerOps.PaintViewer[fingerHandle.pictureViewer, all] };
END
ELSE
BEGIN
MessageWindow.Append[message: "You can change finger data for only yourself or your machine.", clearFirst: TRUE];
MessageWindow.Blink[];
END;
fingerHandle.outer.newVersion ← FALSE;
ViewerOps.PaintViewer[fingerHandle.outer, caption]
END
ELSE
BEGIN
MessageWindow.Append[message: "No object fingered!", clearFirst: TRUE];
MessageWindow.Blink[];
END;
EXITS FingerProblem => {
MessageWindow.Append[
message: SELECT fingerErrorCode FROM
Aborted => "\n... Transaction Aborted; Retry Finger Operation",
Error => "\n... Internal Finger Error; Contact Implementor",
Failure => "\n... Connection with server broken; try again later",
ENDCASE => NIL,
clearFirst: TRUE ];
MessageWindow.Blink[] };
END;
TalkTo: Buttons.ButtonProc =
BEGIN
fingerHandle: FingerHandle = NARROW[clientData]; -- get finger viewer data
userNamePattern: Rope.ROPE = ViewerTools.GetContents[fingerHandle.name.result];
cmd: Commander.Handle ← NEW[Commander.CommandObject ← []];
cmd.out ← IO.noWhereStream;
cmd.err ← IO.noWhereStream;
IF Rope.Length[userNamePattern] # 0
THEN
[] ← CommandTool.DoCommandRope[commandLine: Rope.Cat["///Commands/Talk ", userNamePattern, " &"], parent: cmd]
ELSE
BEGIN
MessageWindow.Append[message: "Enter username for talk connection.", clearFirst: TRUE];
MessageWindow.Blink[];
END;
END;
SelectionButtonClicked: Buttons.ButtonProc =
BEGIN
force the selection into the user input field
whichButton: SelectionData ← NARROW[clientData]; -- get our data
fingerHandle: FingerHandle ← whichButton.parentHandle; -- get other data relating to finger tool
IF whichButton.selectable THEN
IF (whichButton.selected ← NOT whichButton.selected) THEN {
Buttons.SetDisplayStyle[whichButton.button, $WhiteOnBlack];
IF fingerHandle.editable THEN ViewerTools.EnableUserEdits[whichButton.result] }
ELSE
BEGIN
ViewerTools.SetContents[whichButton.result, ""];
fingerHandle.outer.newVersion ← FALSE;
ViewerOps.PaintViewer[fingerHandle.outer, caption];
Buttons.SetDisplayStyle[whichButton.button, $BlackOnWhite];
ViewerTools.InhibitUserEdits[whichButton.result]
END
END;
Register commands with the Exec to perform finger and create an instance of the Finger Tool.
ViewerOps.RegisterViewerClass[flavor: $FingerPicture, class: NEW[ViewerClasses.ViewerClassRec ← [flavor: $FingerPicture, paint: PaintPicture]]];
Commander.Register[key: "FingerTool", proc: MakeFingerTool, doc: "Create a finger tool." ];
END.