DIRECTORY
AIS,
Atom USING [GetPName, MakeAtom],
BasicTime USING [nullGMT, GMT, Now, Period],
Buttons USING [Button, ButtonProc, Create, SetDisplayStyle, Destroy],
Commander USING [CommandProc, Handle, Register],
Containers USING [Container, Create],
Convert USING [RopeFromTime],
FingerOps,
FS USING [Error, ExpandName, FileInfo],
Icons USING [IconFlavor, NewIconFromFile],
Imager,
ImagerBackdoor,
ImagerBox,
ImagerColor,
ImagerTransformation,
ImagerPixelArray,
ImagerFont,
IO,
List USING [Sort, Comparison, CompareProc],
MBQueue,
Menus USING [MouseButton],
MessageWindow,
Process USING[Detach],
ThisMachine,
Rope,
UserCredentials USING [Get],
UserProfile,
VFonts,
ViewerClasses,
ViewerEvents,
ViewerOps,
ViewerTools,
WalnutRegistry;
FingerTool:
CEDAR
PROGRAM
IMPORTS
AIS, Atom, BasicTime, Buttons, Commander, Containers, Convert, FingerOps, FS, Icons, Imager, ImagerBackdoor, ImagerColor, ImagerPixelArray, ImagerTransformation, IO, List, MBQueue, MessageWindow, Process, ThisMachine, Rope, UserCredentials, UserProfile, ViewerEvents, VFonts, ViewerOps, ViewerTools, WalnutRegistry =
BEGIN
FingerImpl: ERROR ~ CODE; -- BugCatchers!
These global lists store the other answers to a query with more than one answer and allow traversing in both directions.
requestMachineList, requestUserList: requestList;
Directions: TYPE ~ {forwards, backwards};
requestList:
TYPE ~
RECORD [
list: LIST OF Rope.ROPE ← NIL,
presentValue: LIST OF Rope.ROPE ← NIL
];
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
nextUserButton: Buttons.Button, -- transient button for multiple matches
nextMachineButton: Buttons.Button, -- transient button for multiple matches
performHostButton, storeHostButton: Buttons.Button,
the following hold all the data pertinent to selections for each fingered object
These items are either user supplied or calculated from other info in the database....
name, host, lastchange, actions: SelectionData ← NEW[SelectionRecord],
...while these items come directly from the database
fingerInfo: LIST OF SelectionData,
pictureViewer: ViewerClasses.Viewer];
SelectionData: TYPE = REF SelectionRecord;
SelectionRecord:
TYPE =
RECORD
[parentHandle: FingerHandle,
prop: ATOM ← NIL,
selectable: BOOL,
button: Buttons.Button,
result: ViewerClasses.Viewer,
registration: ViewerEvents.EventRegistration ← NIL];
PictureState: TYPE = REF PictureStateRecord;
PictureStateRecord:
TYPE =
RECORD
[rectangle: ImagerBox.Rectangle,
pa: ImagerPixelArray.PixelArray,
colorOperator: Imager.ColorOperator];
fingerIcon: Icons.IconFlavor = Icons.NewIconFromFile["Finger.icon", 0];
fingerQueue: MBQueue.Queue = MBQueue.Create[];
mailProp: FingerOps.PropPair ← NEW[FingerOps.PropPairObject ← [prop:$MailRead, val: NIL]];
IsPattern:
PROC [value: Rope.
ROPE]
RETURNS [
ItsAPattern:
BOOL] ~ {
... returns TRUE iff value is has a "*" in it.
RETURN [Rope.Find[s1: value, s2: "*", pos1: 0, case: FALSE] # -1]
}; --
Append:
PROC [list1, list2:
LIST
OF
ATOM]
RETURNS [
LIST
OF
ATOM] ~ {
I need my own version of Append 'cos I couldn't easily use exsisting stuff.
newList: LIST OF ATOM ← list1;
FOR list:
LIST
OF
ATOM ← list2, list.rest
UNTIL list =
NIL
DO
newList ← CONS[list.first, newList]
ENDLOOP;
RETURN[newList]
};
InAtomList:
PROC [atom:
ATOM, atomList:
LIST
OF
ATOM]
RETURNS [inList:
BOOLEAN] ~ {
... returns TRUE iff atom is in atomList
FOR list:
LIST
OF
ATOM ← atomList, list.rest
UNTIL list =
NIL
DO
IF atom = list.first THEN RETURN[inList ← TRUE]
ENDLOOP;
RETURN[inList ← FALSE]
}; -- InAtomList
RecordMailRead: WalnutRegistry.EventProc ~ {
IF event = mailRead
THEN {
mailProp.val ← TimeRope[BasicTime.Now[]];
FingerOps.SetUserProps[user: UserCredentials.Get[].name, props: LIST[ mailProp] ! FingerOps.FingerError => CONTINUE ] } };
InitFingerInfo: PROC [fingerHandle: FingerHandle]
Sets up the fingerInfo part of fingerHandle with all the attributes the database knows about.
~ {
FOR info:
LIST
OF
ATOM ← Append[FingerOps.ListMachineProps[], FingerOps.ListUserProps[]], info.rest
UNTIL info =
NIL
DO
fingerHandle.fingerInfo ← CONS[NEW[SelectionRecord], fingerHandle.fingerInfo];
fingerHandle.fingerInfo.first.prop ← info.first
ENDLOOP;
}; -- InitFingerInfo
FindDataForButton:
PROC [button:
ATOM, fingerInfo:
LIST
OF SelectionData]
RETURNS [SelectionData ←
NIL] ~ {
Returns the relevant part of fingerInfo corresponding to button.
FOR infoList:
LIST
OF SelectionData ← fingerInfo, infoList.rest
UNTIL infoList =
NIL
DO
IF button = infoList.first.prop THEN RETURN[infoList.first]
ENDLOOP;
ERROR FingerImpl; -- should never get to here
}; -- FindDataForButton
ProduceButtonName: PROC [buttonAtom: ATOM] RETURNS [buttonName: Rope.ROPE]
This returns a text version of the atom. It originally was going to do some tricky stuff with the pname (hence the separate routine) but that was later changed.
~ {
buttonName ← Atom.GetPName[atom: buttonAtom];
}; -- ProduceButtonName
InvalidateInfo:
PROC [buttonSet:
LIST
OF
ATOM, fingerHandle: FingerHandle] ~ {
This will clear the entries of the buttons listed in buttonSet in the finger tool.
FOR button:
LIST
OF
ATOM ← buttonSet, button.rest
UNTIL button =
NIL
DO
ViewerTools.SetContents[FindDataForButton[button: button.first, fingerInfo: fingerHandle.fingerInfo].result, NIL];
ENDLOOP;
};
Alphabetisize: PROC [atomSet: LIST OF ATOM] RETURNS [LIST OF ATOM]
...returns atomSet ordered alphabetically by PName.
~ {
Compare: List.CompareProc = {
c:List.Comparison;
WITH ref1
SELECT
FROM
rope: Rope.ROPE => c ← Rope.Compare[s1: rope, s2: NARROW[ref2], case: TRUE];
ENDCASE =>
IF ref1 =
NIL
THEN c ← Rope.Compare[s1: NARROW[ref1], s2: NARROW[ref2], case: TRUE]
ELSE ERROR;
RETURN [IF c=less THEN greater ELSE IF c=greater THEN less ELSE equal]
};
pNameSet: LIST OF REF ANY ← NIL;
alphaSet: LIST OF ATOM ← NIL;
FOR atom:
LIST
OF
ATOM ← atomSet, atom.rest
UNTIL atom =
NIL
DO
pNameSet ← CONS[Atom.GetPName[atom: atom.first], pNameSet]
ENDLOOP;
FOR pName:
LIST
OF
REF
ANY ← List.Sort[list: pNameSet, compareProc: Compare], pName.rest
UNTIL pName =
NIL
DO
alphaSet ← CONS[Atom.MakeAtom[pName: NARROW[pName.first, Rope.ROPE]], alphaSet]
ENDLOOP;
RETURN[alphaSet]
};
MakeFingerTool: Commander.CommandProc =
BEGIN
fingerHandle: FingerHandle = NEW[FingerToolRec];
InitFingerInfo[fingerHandle: fingerHandle];
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]]; -- don't allow user to scroll contents
fingerHandle.height ← 0;
SetupViewer[fingerHandle];
ViewerOps.SetOpenHeight[fingerHandle.outer, fingerHandle.height]; -- hint our desired height
ViewerOps.PaintViewer[fingerHandle.outer, all]; -- reflect above change
set up the user and machine fields with default values
DisplayMachineInfo[fingerHandle: fingerHandle, machine: ThisMachine.Name[$Pup]];
DisplayUserInfo[fingerHandle: fingerHandle, user: UserCredentials.Get[].name, mouseButton: red]
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;
hostButtonsLevel: CARDINAL ← 0; -- set to the vertical level of the host buttons.
userButtonsLevel: CARDINAL ← 0; -- set to the vertical level of the user buttons.
pictureFont: ImagerFont.Font =
VFonts.EstablishFont["Helvetica", 12, TRUE];
SetupViewer:
PROC [fingerHandle: FingerHandle] =
BEGIN
SetupButtons:
PROC [name: Rope.
ROPE, which: SelectionData, scroll:
BOOL ←
FALSE, height:
CARDINAL ← entryHeight, edit:
BOOL ←
FALSE, style:
ATOM ← $WhiteOnBlack] =
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.selectable ← edit;
Buttons.SetDisplayStyle[which.button, style];
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; -- SetupButtons
SetupButtonSet: PROC [buttonSet: LIST OF ATOM, fingerInfo: LIST OF SelectionData]
Takes a list of atoms and produces a vertical set of buttons in the fingertool based on those atoms and related to fingerInfo in fingerHandle.
~ {
data: SelectionData;
FOR button:
LIST
OF
ATOM ← Alphabetisize[buttonSet], button.rest
UNTIL button =
NIL
DO
data ← FindDataForButton[button: button.first, fingerInfo: fingerInfo];
SetupButtons[
name: ProduceButtonName[button.first],
which: data,
scroll: TRUE,
edit: TRUE];
data.registration ← ViewerEvents.RegisterEventProc[SetNew, edit, data.result]
ENDLOOP;
}; -- SetupButtonSet
fingerHandle.height ← fingerHandle.height + entryVSpace; -- space down from the top of the viewer
userButtonsLevel ← fingerHandle.height;
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];
-- Can't use talker yet, because it hasn't been carried over
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: "User Name", which: fingerHandle.name, scroll: TRUE, edit: TRUE];
fingerHandle.name.registration ← ViewerEvents.RegisterEventProc[SetNew, edit, fingerHandle.name.result];
SetupButtonSet[buttonSet: FingerOps.ListUserProps[], fingerInfo: fingerHandle.fingerInfo];
SetupButtons[name: "Actions", which: fingerHandle.actions, scroll: TRUE, height: 3*entryHeight, edit: FALSE, style: $BlackOnGrey];
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: FALSE, scrollable: FALSE],
paint: TRUE];
host part of the screen
fingerHandle.height ← fingerHandle.height + entryVSpace; -- space down a line
hostButtonsLevel ← fingerHandle.height;
fingerHandle.performHostButton ← Buttons.Create
[info:
[name: "Get Host Information",
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: PerformHost];
fingerHandle.storeHostButton ← Buttons.Create
-- button for making changes to finger data
[info:
[name: "Store Host Data",
wx: fingerHandle.performFingerButton.wx + fingerHandle.performHostButton.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: StoreHost];
fingerHandle.height ← fingerHandle.height + entryHeight + entryVSpace; -- space down from the action buttons
SetupButtons[name: "Host", which: fingerHandle.host, scroll: TRUE, edit: TRUE];
fingerHandle.host.registration ← ViewerEvents.RegisterEventProc[SetNew, edit, fingerHandle.host.result];
SetupButtonSet[buttonSet: FingerOps.ListMachineProps[], fingerInfo: fingerHandle.fingerInfo];
SetupButtons[name: "Last User", which: fingerHandle.lastchange, scroll: TRUE, height: entryHeight, edit: FALSE, style: $BlackOnGrey];
fingerHandle.height ← fingerHandle.height + entryVSpace; -- space at bottom of finger viewer
END;
SetNew: ViewerEvents.EventProc = {
WHILE viewer.parent # NIL DO viewer ← viewer.parent ENDLOOP;
TRUSTED { Process.Detach[FORK ViewerOps.PaintViewer[viewer: viewer, hint: caption]] }
};
PaintPicture: ViewerClasses.PaintProc = {
state: PictureState = NARROW[self.data];
rect: Imager.Rectangle = ImagerBackdoor.GetBounds[context];
IF state =
NIL
THEN {
Imager.SetColor[context, Imager.white];
Imager.MaskRectangle[context, rect];
Imager.SetColor[context, Imager.black];
Imager.SetXY[context, [3.0, pictureHeight*0.5]];
Imager.SetFont[context, pictureFont];
Imager.ShowRope[context, "No Picture"];
RETURN
};
BEGIN
scaleX: REAL ← self.ww / state.rectangle.w;
scaleY: REAL ← self.wh / state.rectangle.h;
scale: REAL ← MIN[scaleX, scaleY];
Imager.ScaleT[context, scale];
IF state.colorOperator #
NIL
THEN Imager.SetSampledColor[context: context, pa: state.pa, m: NIL, colorOperator: state.colorOperator]
ELSE Imager.SetSampledBlack[context: context, pa: state.pa, m: NIL, clear: FALSE];
Imager.MaskRectangle[context, state.rectangle];
END
};
Message:
PROC [r: Rope.
ROPE] = {
MessageWindow.Clear[];
MessageWindow.Append[r];
MessageWindow.Blink[];
};
DetermineRequest: PROC [fingerHandle: FingerHandle, relation: ATOM] RETURNS [requestedProp: ATOM, requestedValue: Rope.ROPE]
...looks in fingerHandle and makes a wild guess as to which field the query is being done on. relation specifies which relation to look in ($User for the user relation and $Machine for the machine relation). It also acts as the default. If more than one field has something in it, default. If the defalut field is empty then use the local machine/logged user as the default.
~ {
requestedProp ← relation;
requestedValue ← NIL; -- since the default case pays no attention to this value
IF Rope.Length[ViewerTools.GetContents[
IF relation = $User
THEN fingerHandle.name.result
ELSE fingerHandle.host.result]] # 0
THEN {
something there so return default
RETURN
}
ELSE {
full: BOOLEAN ← FALSE;
FOR prop:
LIST
OF
ATOM ← (
IF relation = $User
THEN FingerOps.ListUserProps[]
ELSE FingerOps.ListMachineProps[]), prop.rest
UNTIL prop =
NIL
DO
IF Rope.Length[ViewerTools.GetContents[FindDataForButton[button: prop.first, fingerInfo: fingerHandle.fingerInfo].result]] # 0
THEN
IF full
THEN {
requestedProp ← relation;
requestedValue ← NIL -- since the default case pays no attention to this value
}
ELSE {
full ← TRUE;
requestedProp ← prop.first;
requestedValue ← ViewerTools.GetContents[FindDataForButton[button: prop.first, fingerInfo: fingerHandle.fingerInfo].result]
}
ENDLOOP
}
}; -- DetermineRequest
NextUser:
PROC [fingerHandle: FingerHandle, direction: Directions, picture:
BOOL ←
FALSE] ~ {
... displays the next user on the requestUserList list (going in the right direction).
user: Rope.ROPE;
IF direction = forwards
THEN {
IF requestUserList.presentValue =
NIL
THEN
requestUserList.presentValue ← requestUserList.list
ELSE
IF (requestUserList.presentValue ← requestUserList.presentValue.rest) =
NIL
THEN
requestUserList.presentValue ← requestUserList.list;
user ← requestUserList.presentValue.first;
DisplayUserProps[user: user, fingerHandle: fingerHandle, picture: picture];
}
ELSE {
tempList: LIST OF Rope.ROPE ← requestUserList.list;
find the one before presentValue
IF requestUserList.presentValue = requestUserList.list
THEN
WHILE tempList.rest #
NIL
DO
tempList ← tempList.rest
ENDLOOP
ELSE
WHILE tempList.rest # requestUserList.presentValue
DO
tempList ← tempList.rest
ENDLOOP;
requestUserList.presentValue ← tempList;
user ← requestUserList.presentValue.first;
DisplayUserProps[user: user, fingerHandle: fingerHandle, picture: picture];
}
};
DisplayUserProps: PROC [user: Rope.ROPE, fingerHandle: FingerHandle, picture: BOOL ← FALSE]
...displays all the information about user in the fingertool.
~ {
lastChange: FingerOps.StateChange;
time: BasicTime.GMT;
now: BasicTime.GMT = BasicTime.Now[];
twoDaysInSeconds: INT = LONG[48] * 60 * 60;
muser: Rope.ROPE;
action: Rope.ROPE ← " ";
First check the actions entry
FOR m:
LIST
OF Rope.
ROPE ← FingerOps.GetUserData[user], m.rest
UNTIL m=NIL DO
[lastChange, time, muser] ← FingerOps.GetMachineData[m.first];
If the entry is real old, don't bother displaying it
IF BasicTime.Period[from: time, to: now] > twoDaysInSeconds THEN LOOP;
action ← Rope.Cat[action, muser, Rope.Cat[
IF lastChange=FingerOps.StateChange[login]
THEN " logged in "
ELSE " logged out ", m.first],
Rope.Cat[" at\n ",TimeRope[time],"\n"]];
ENDLOOP;
ViewerTools.SetContents[fingerHandle.actions.result, action];
ViewerTools.SetContents[fingerHandle.name.result, user]; -- do it again if necessary
FOR p:
LIST
OF FingerOps.PropPair ← FingerOps.GetUserProps[user], p.rest
UNTIL p=NIL DO
IF p.first.prop=Atom.MakeAtom["Picture File"]
THEN {
SetPicture[fingerHandle.pictureViewer, IF picture THEN p.first.val ELSE NIL];
};
ViewerTools.SetContents[FindDataForButton[button: p.first.prop, fingerInfo: fingerHandle.fingerInfo].result, p.first.val];
ENDLOOP;
}; -- NextUser
NextMachine:
PROC [fingerHandle: FingerHandle, direction: Directions] ~ {
... displays the next machine on the requestMachineList list (going in the right direction).
machine: Rope.ROPE;
IF direction = forwards
THEN {
IF requestMachineList.presentValue = NIL THEN requestMachineList.presentValue ← requestMachineList.list
ELSE
IF (requestMachineList.presentValue ← requestMachineList.presentValue.rest) = NIL THEN requestMachineList.presentValue ← requestMachineList.list;
machine ← requestMachineList.presentValue.first;
DisplayMachineProps[machine: machine, fingerHandle: fingerHandle];
}
ELSE {
tempList: LIST OF Rope.ROPE ← requestMachineList.list;
find the one before presentValue
IF requestMachineList.presentValue = requestMachineList.list
THEN
WHILE tempList.rest #
NIL
DO
tempList ← tempList.rest
ENDLOOP
ELSE
WHILE tempList.rest # requestMachineList.presentValue
DO
tempList ← tempList.rest
ENDLOOP;
requestMachineList.presentValue ← tempList;
machine ← requestMachineList.presentValue.first;
DisplayMachineProps[machine: machine, fingerHandle: fingerHandle];
}
}; -- NextMachine
DisplayMachineProps: PROC [machine: Rope.ROPE, fingerHandle: FingerHandle]
...displays all the information about machine in the fingertool.
~ {
lastChange: FingerOps.StateChange;
time: BasicTime.GMT;
lastUser: Rope.ROPE ← " ";
First check for any changes in the lastuser
[lastChange, time, lastUser] ← FingerOps.GetMachineData[machine];
IF
NOT Rope.Equal[lastUser, ""]
THEN
ViewerTools.SetContents[fingerHandle.lastchange.result,
Rope.Cat[IF lastChange = FingerOps.StateChange[login] THEN "Login " ELSE
"Logout ", lastUser, " at ",TimeRope[time]]]
ELSE ViewerTools.SetContents[fingerHandle.lastchange.result, NIL];
ViewerTools.SetContents[fingerHandle.host.result, machine]; -- do it again if necessary
FOR p:
LIST
OF FingerOps.PropPair ← FingerOps.GetMachineProps[machine], p.rest
UNTIL p=NIL DO
ViewerTools.SetContents[FindDataForButton[button: p.first.prop, fingerInfo: fingerHandle.fingerInfo].result, p.first.val];
ENDLOOP;
}; -- DisplayMachineProps
LookUpMachineProp:
PROC [fingerHandle: FingerHandle, requestedProp:
ATOM, requestedValue: Rope.
ROPE] ~ {
...looks up the machine relation for the relshipSet that matches requestedValue on the requestedProp attribute.
IF Rope.Length[base: requestedValue] = 0 THEN RETURN;
requestMachineList.list ← NIL; -- make sure there is nothing left over fro last time.
requestMachineList.list ← FingerOps.MatchMachineProperty[propVal: NEW[FingerOps.PropPairObject ← [prop: requestedProp, val: requestedValue]]];
requestMachineList.presentValue ← NIL;
IF requestMachineList.list =
NIL
THEN {
Message["Not valid!"];
InvalidateInfo[buttonSet: FingerOps.ListMachineProps[], fingerHandle: fingerHandle];
ViewerTools.SetContents[fingerHandle.lastchange.result, NIL];
RETURN;
};
NextMachine[fingerHandle: fingerHandle, direction: forwards];
if there is more than one match, put up the NEXT button.
IF requestMachineList.list.rest #
NIL
THEN
fingerHandle.nextMachineButton ← Buttons.Create
[info:
[name: "Next",
wx: fingerHandle.storeHostButton.wx + fingerHandle.storeHostButton.ww + horizSpace,
wy: hostButtonsLevel,
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: PerformNextMachine];
}; -- LookUpMachineProp
LookUpUserProp:
PROC [fingerHandle: FingerHandle, requestedProp:
ATOM, requestedValue: Rope.
ROPE] ~ {
...looks up the user relation for the relshipSet that matches requestedValue on the requestedProp attribute.
IF Rope.Length[base: requestedValue] = 0 THEN RETURN;
requestUserList.list ← NIL;
requestUserList.list ← FingerOps.MatchUserProperty[propVal: NEW[FingerOps.PropPairObject ← [prop: requestedProp, val: requestedValue]]];
requestUserList.presentValue ← NIL;
IF requestUserList.list =
NIL
THEN {
Message["Not a valid user!"];
SetPicture[fingerHandle.pictureViewer, NIL];
InvalidateInfo[buttonSet: FingerOps.ListUserProps[], fingerHandle: fingerHandle];
ViewerTools.SetContents[fingerHandle.actions.result, NIL];
RETURN;
};
NextUser[fingerHandle: fingerHandle, direction: forwards];
if there is more than one match, put up the NEXT button.
IF requestUserList.list.rest #
NIL
THEN
fingerHandle.nextUserButton ← Buttons.Create
[info:
[name: "Next",
wx: fingerHandle.storeChangesButton.wx + fingerHandle.storeChangesButton.ww + horizSpace,
wy: userButtonsLevel,
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: PerformNextUser]
}; -- LookUpUserProp
DisplayUserInfo:
PROC [fingerHandle: FingerHandle, user: Rope.
ROPE, mouseButton: Menus.MouseButton ← red] ~ {
... displays all the info for user.
IF Rope.Length[user] = 0
THEN {
user ← UserCredentials.Get[].name;
ViewerTools.SetContents[fingerHandle.name.result, user];
};
requestUserList.list ← NIL;
requestUserList.presentValue ←
NIL;
Because noone ever bothers to put the registration, append a "*" (assuming there isn't already one there) and always look up the pattern. It's pretty quite so noone will no the difference...
requestUserList.list ← FingerOps.GetMatchingPersons[pattern: IF NOT IsPattern[user] THEN Rope.Concat[base: user, rest: "*"] ELSE user];
IF (requestUserList.list =
NIL)
THEN {
IF IsPattern[user] THEN Message["No Match"] ELSE Message[Rope.Cat[user, " not found in Finger database!"]];
SetPicture[fingerHandle.pictureViewer, NIL];
InvalidateInfo[buttonSet: FingerOps.ListUserProps[], fingerHandle: fingerHandle];
ViewerTools.SetContents[fingerHandle.actions.result, NIL];
RETURN;
};
display all the properties of the first machine
NextUser[fingerHandle: fingerHandle, direction: forwards, picture: mouseButton = blue];
if there is more than one match, put up the NEXT button.
IF requestUserList.list.rest #
NIL
THEN
fingerHandle.nextUserButton ← Buttons.Create
[info:
[name: "Next",
wx: fingerHandle.storeChangesButton.wx + fingerHandle.storeChangesButton.ww + horizSpace,
wy: userButtonsLevel,
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: PerformNextUser]
}; -- DisplayUserInfo
DisplayMachineInfo:
PROC [fingerHandle: FingerHandle, machine: Rope.
ROPE] ~ {
... displays all the info for machine.
IF Rope.Length[machine]=0
THEN {
machine ← ThisMachine.Name[$Pup];
ViewerTools.SetContents[fingerHandle.host.result, machine];
};
see if host exists
IF
NOT FingerOps.MachineExists[machine]
AND
NOT IsPattern[machine]
THEN {
Message[Rope.Cat[machine," is not a valid host!"]];
InvalidateInfo[buttonSet: FingerOps.ListMachineProps[], fingerHandle: fingerHandle];
ViewerTools.SetContents[fingerHandle.lastchange.result, NIL];
RETURN;
};
requestMachineList.list ← NIL; -- make sure there is nothing left over fro last time.
requestMachineList.presentValue ← NIL;
see if machine is really a pattern and do the right thing
IF IsPattern[machine]
THEN
requestMachineList.list ← FingerOps.GetMatchingMachines[pattern: machine]
ELSE
requestMachineList.list ← LIST[machine];
IF (requestMachineList.list =
NIL)
AND IsPattern[machine]
THEN {
Message["No Match"];
InvalidateInfo[buttonSet: FingerOps.ListMachineProps[], fingerHandle: fingerHandle];
ViewerTools.SetContents[fingerHandle.lastchange.result, NIL];
RETURN;
};
display all the properties of the first machine
NextMachine[fingerHandle: fingerHandle, direction: forwards] ;
if there is more than one match, put up the NEXT button.
IF requestMachineList.list.rest #
NIL
THEN
fingerHandle.nextMachineButton ← Buttons.Create
[info:
[name: "Next",
wx: fingerHandle.storeHostButton.wx + fingerHandle.storeHostButton.ww + horizSpace,
wy: hostButtonsLevel,
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: PerformNextMachine];
}; -- DisplayMachineInfo
fingerErrorCode: FingerOps.Reason;
PerformNextUser: Buttons.ButtonProc ~ {
...displays the information about the first user in requestUserList in fingertool and the removes that user from the list except from a button request.
Note that the only time this can be called is when the button exists so there shouldn't be any problems destroying it...right?
ENABLE
FingerOps.FingerError => {fingerErrorCode ← reason; GOTO FingerProblem };
fingerHandle: FingerHandle ← NARROW[clientData];
IF mouseButton = yellow THEN RETURN;
IF mouseButton = blue THEN NextUser[fingerHandle: fingerHandle, direction: backwards] ELSE NextUser[fingerHandle: fingerHandle, direction: forwards];
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[] };
}; -- PerformNextUser
PerformNextMachine: Buttons.ButtonProc
...displays the information about the first machine in requestMachineList in fingertool and the removes that machine from the list except from a button request.
Note that the only time this can be called is when the button exists so there shouldn't be any problems destroying it...right?
~ {
ENABLE
FingerOps.FingerError => {fingerErrorCode ← reason; GOTO FingerProblem };
fingerHandle: FingerHandle ← NARROW[clientData];
IF mouseButton = yellow THEN RETURN;
NextMachine[fingerHandle: fingerHandle, direction: IF mouseButton = blue THEN backwards ELSE forwards]
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[] };
}; -- PerformNextMachine
PerformHost: Buttons.ButtonProc =
BEGIN
ENABLE
FingerOps.FingerError => {fingerErrorCode ← reason; GOTO FingerProblem };
fingerHandle: FingerHandle ← NARROW[clientData];
machine: Rope.ROPE ← ViewerTools.GetContents[fingerHandle.host.result];
requestedProp: ATOM;
requestedValue: Rope.ROPE;
Remove the "Next" button.
Buttons.Destroy[fingerHandle.nextMachineButton];
Decide which field to do the lookup on. If more than one field has something in it, default to the "Host" field. I the "Host" field is empty then default to the host machine.
[requestedProp, requestedValue] ← DetermineRequest[fingerHandle, $Machine];
IF requestedProp = $Machine -- ie default -- THEN DisplayMachineInfo[fingerHandle: fingerHandle, machine: machine]
ELSE LookUpMachineProp[fingerHandle: fingerHandle, requestedProp: requestedProp, requestedValue: requestedValue];
fingerHandle.outer.newVersion ← FALSE;
ViewerOps.PaintViewer[fingerHandle.outer, caption];
now set editable bit (for now, let anybody edit everything!)
fingerHandle.editable ← Rope.Equal[user, self, FALSE];
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; -- PerformHost
StoreHost: Buttons.ButtonProc =
BEGIN
ENABLE
FingerOps.FingerError => {fingerErrorCode ← reason; GOTO FingerProblem };
fingerHandle: FingerHandle = NARROW[clientData]; -- get finger viewer data
newInfo: LIST OF FingerOps.PropPair ← NIL;
IF Rope.Length[ViewerTools.GetContents[fingerHandle.host.result]] = 0 THEN RETURN;
FOR prop:
LIST
OF
ATOM ← FingerOps.ListMachineProps[], prop.rest
UNTIL prop =
NIL
DO
newInfo ← CONS[NEW[FingerOps.PropPairObject ← [prop.first, ViewerTools.GetContents[FindDataForButton[button: prop.first, fingerInfo: fingerHandle.fingerInfo].result]]], newInfo]
ENDLOOP;
FingerOps.SetMachineProps[ViewerTools.GetContents[fingerHandle.host.result], newInfo];
fingerHandle.outer.newVersion ← FALSE;
ViewerOps.PaintViewer[fingerHandle.outer, caption];
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; -- StoreHost
PerformFingerAtTool: Buttons.ButtonProc =
BEGIN
ENABLE
FingerOps.FingerError => {fingerErrorCode ← reason; GOTO FingerProblem };
fingerHandle: FingerHandle ← NARROW[clientData];
user: Rope.ROPE ← ViewerTools.GetContents[fingerHandle.name.result];
now: BasicTime.GMT = BasicTime.Now[];
twoDaysInSeconds: INT = LONG[48] * 60 * 60;
action: Rope.ROPE ← " ";
requestedProp: ATOM;
requestedValue: Rope.ROPE;
Remove the "Next" button.
Buttons.Destroy[fingerHandle.nextUserButton];
Decide which field to do the lookup on. If more than one field has something in it, default to the User field. I the User field is empty then default to the user that is logged in.
[requestedProp, requestedValue] ← DetermineRequest[fingerHandle, $User];
IF requestedProp = $User THEN DisplayUserInfo[fingerHandle: fingerHandle, user: user, mouseButton: mouseButton]
ELSE LookUpUserProp[fingerHandle: fingerHandle, requestedProp: requestedProp, requestedValue: requestedValue];
fingerHandle.outer.newVersion ← FALSE;
ViewerOps.PaintViewer[fingerHandle.outer, caption];
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; -- PerformFingerAtTool
StoreChanges: Buttons.ButtonProc =
BEGIN
ENABLE
FingerOps.FingerError => {fingerErrorCode ← reason; GOTO FingerProblem };
fingerHandle: FingerHandle = NARROW[clientData]; -- get finger viewer data
aisFile: Rope.ROPE ← ViewerTools.GetContents[FindDataForButton[button: Atom.MakeAtom["Picture File"], fingerInfo: fingerHandle.fingerInfo].result]; -- needed for SetPicture
newInfo: LIST OF FingerOps.PropPair ← NIL;
IF Rope.Length[ViewerTools.GetContents[fingerHandle.name.result]] = 0 THEN RETURN;
FOR buttonVal:
LIST
OF SelectionData ← fingerHandle.fingerInfo, buttonVal.rest
UNTIL buttonVal =
NIL
DO
IF InAtomList[atom: buttonVal.first.prop, atomList: FingerOps.ListUserProps[]]
THEN
newInfo ← CONS[NEW[FingerOps.PropPairObject ← [prop: buttonVal.first.prop, val: ViewerTools.GetContents[buttonVal.first.result]]], newInfo];
ENDLOOP;
FingerOps.SetUserProps[ViewerTools.GetContents[fingerHandle.name.result], newInfo];
IF mouseButton = blue THEN SetPicture[fingerHandle.pictureViewer, aisFile];
fingerHandle.outer.newVersion ← FALSE;
ViewerOps.PaintViewer[fingerHandle.outer, caption];
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;
TimeRope:
PROC[time: BasicTime.
GMT]
RETURNS[rope: Rope.
ROPE] = {
rope ←
IF time = BasicTime.nullGMT
THEN "unknown"
ELSE Convert.RopeFromTime[time] };
SetPicture:
PROC[v: ViewerClasses.Viewer, filename: Rope.
ROPE] ~ {
v.data ← NIL;
BEGIN
ENABLE
FS.Error =>
CONTINUE;
IF Rope.Length[filename] = 0 THEN NULL
ELSE
IF
FS.ExpandName[filename].cp.ext.length # 0
THEN {
--suppose it is a single black-and-white AIS file name
pa: ImagerPixelArray.PixelArray ← ImagerPixelArray.FromAIS[filename];
maxSample: ImagerPixelArray.Sample ← ImagerPixelArray.MaxSampleValue[pa, 0];
r: ImagerBox.Rectangle ← ImagerTransformation.TransformRectangle[pa.m, [0, 0, pa.sSize, pa.fSize]];
aisFile: AIS.FRef ← AIS.OpenFile[filename];
colorOperator: Imager.ColorOperator ←
SELECT aisFile.raster.bitsPerPixel
FROM
0 => oneBPPInverted,
1 => oneBPPPositive,
0 => NIL,
ENDCASE => ImagerColor.NewColorOperatorGrayLinear[maxSample, 0, maxSample+1, NIL];
AIS.CloseFile[aisFile];
v.data ← NEW[PictureStateRecord ← [rectangle: r, pa: pa, colorOperator: colorOperator]];
}
ELSE {
--suppose it is the stem of the file names for the RGB seperations of a color picture
redName: Rope.ROPE ← GetSep[filename, "AISSeparationKeys.red", LIST["-red", "-r"]];
greenName: Rope.ROPE ← GetSep[filename, "AISSeparationKeys.green", LIST["-grn", "-green", "-g"]];
blueName: Rope.ROPE ← GetSep[filename, "AISSeparationKeys.blue", LIST["-blu", "-blue", "-b"]];
pa: ImagerPixelArray.PixelArray ← ImagerPixelArray.Join3AIS[redName, greenName, blueName];
maxSample: ImagerPixelArray.Sample ← ImagerPixelArray.MaxSampleValue[pa, 0];
r: ImagerBox.Rectangle ← ImagerTransformation.TransformRectangle[pa.m, [0, 0, pa.sSize, pa.fSize]];
colorOperator: Imager.ColorOperator ← ImagerColor.NewColorOperatorRGB[maxSample];
v.data ← NEW[PictureStateRecord ← [rectangle: r, pa: pa, colorOperator: colorOperator]];
};
END;
ViewerOps.PaintViewer[v, all];
}; -- SetPicture
RopeList: TYPE = LIST OF Rope.ROPE;
GetSep:
PROC [fileStem, sepKey: Rope.
ROPE, sepDefault: RopeList]
RETURNS [fileName: Rope.
ROPE] = {
seps: RopeList ← UserProfile.ListOfTokens[sepKey, sepDefault];
exts: RopeList ← UserProfile.ListOfTokens["AISExtensions", LIST["AIS"]];
sl, el: Rope.ROPE ← NIL;
FOR ss: RopeList ← seps, ss.rest
WHILE ss #
NIL
DO
IF sl = NIL THEN sl ← ss.first ELSE sl ← sl.Cat[", ", ss.first];
FOR es: RopeList ← exts, es.rest
WHILE es #
NIL
DO
exists: BOOL ← TRUE;
fileName ← Rope.Cat[fileStem, ss.first, ".", es.first];
[] ← FS.FileInfo[fileName !FS.Error => {exists ← FALSE; CONTINUE}];
IF exists THEN RETURN;
ENDLOOP;
ENDLOOP;
FOR es: RopeList ← exts, es.rest
WHILE es #
NIL
DO
IF el = NIL THEN el ← es.first ELSE el ← el.Cat[", ", es.first];
ENDLOOP;
FS.Error[[user, $NoSuchFile,
IO.PutFR[
"%g{%g}.{%g}",
[rope[fileStem]],
[rope[sl]],
[rope[el]]
]]];
}; -- GetSep
GetSep: PROC [fileStem, fmt: Rope.ROPE] RETURNS [fileName: Rope.ROPE] = {
PerCandidate: PROC [rope: Rope.ROPE] RETURNS [tryNext: BOOL ← TRUE] = {
exists: BOOL ← TRUE;
[] ← FS.FileInfo[rope !FS.Error => {exists ← FALSE; CONTINUE}];
IF exists THEN fileName ← rope;
tryNext ← NOT exists;
};
fileName ← NIL;
UserProfileOps.Enumerate[PerCandidate, fmt, IO.rope[fileStem]];
IF fileName = NIL THEN FS.Error[[user, $NoSuchFile, fmt.Cat[" of ", fileStem]]];
};
oneBPPPositive: Imager.ColorOperator ← ImagerColor.NewColorOperatorMap[1, MapPositive];
oneBPPInverted: Imager.ColorOperator ← ImagerColor.NewColorOperatorMap[1, MapInverted];
MapPositive:
PROC [s: ImagerPixelArray.Sample]
RETURNS [Imager.ConstantColor] = {
RETURN [
SELECT s
FROM
0 => Imager.black,
1 => Imager.white,
ENDCASE => ERROR];
};
MapInverted:
PROC [s: ImagerPixelArray.Sample]
RETURNS [Imager.ConstantColor] = {
RETURN [
SELECT s
FROM
0 => Imager.white,
1 => Imager.black,
ENDCASE => ERROR];
};
-- Can't use talker yet, when it gets converted, we'll use it
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;
SetUserProperties: Commander.CommandProc = {
ENABLE FingerOps.FingerError => { fingerErrorCode ← reason; GOTO FingerProblem };
FingerOps.AddUserProp["Plan"];
FingerOps.AddMachineProp["Network"];
EXITS FingerProblem => {
IO.PutRope[cmd.out,
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 ] };
};
SelectionButtonClicked: Buttons.ButtonProc ~ {
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 {
ViewerTools.EnableUserEdits[whichButton.result];
ViewerTools.SetSelection[whichButton.result];
Now decide what to do. If it's the middle button that's clicked then do a lookup on whatever is in that field (whatever that field is).
IF mouseButton = yellow
THEN {
IF whichButton = fingerHandle.name
THEN {
Buttons.Destroy[fingerHandle.nextUserButton];
DisplayUserInfo[fingerHandle: fingerHandle, user: ViewerTools.GetContents[whichButton.result]]
}
ELSE
IF whichButton = fingerHandle.host
THEN {
Buttons.Destroy[fingerHandle.nextMachineButton];
DisplayMachineInfo[fingerHandle: fingerHandle, machine: ViewerTools.GetContents[whichButton.result]]
}
ELSE
IF InAtomList[atom: whichButton.prop, atomList: FingerOps.ListMachineProps[]]
THEN {
Buttons.Destroy[fingerHandle.nextMachineButton];
LookUpMachineProp[fingerHandle: fingerHandle, requestedProp: whichButton.prop, requestedValue: ViewerTools.GetContents[whichButton.result]]
}
ELSE {
Buttons.Destroy[fingerHandle.nextUserButton];
LookUpUserProp[fingerHandle: fingerHandle, requestedProp: whichButton.prop, requestedValue: ViewerTools.GetContents[whichButton.result]]
}
}
}
}; -- SelectionButtonClicked
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." ];
Commander.Register[key: "SetUserProperties", proc: SetUserProperties, doc: "Initialize all of the necessary user properties for the FingerTool"];
[] ← WalnutRegistry.Register[procSet: WalnutRegistry.ProcSet[eventProc: RecordMailRead], queue: fingerQueue];
END.