<> <> <> <<>> 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], <> 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; FingerToolImpl: 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) <> name, actualName, pictureFileName, plan: SelectionData _ NEW[SelectionRecord], opt1, opt2, opt3: SelectionData _ NEW[SelectionRecord], <> editable: BOOL _ FALSE, -- if the current entry is the logged-in user or this machine, then the fields of the entry can be changed pictureAISFile: Rope.ROPE, <> <> 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; <> 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: BOOL _ FALSE, height: CARDINAL _ entryHeight, edit: BOOL _ FALSE] = 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]; <> <<[viewerRec:>> <<[wx: firstColumnIndent + firstColumnWidth + horizSpace + secondColumnWidth + horizSpace,>> <> <> <> <> <> <> <> <<[gallery: fingerHandle.pictureGallery, >> <> <> <> <> <> 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: REAL _ MIN[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 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]; <> 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]; < 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 <> 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; <> ViewerOps.RegisterViewerClass[flavor: $FingerPicture, class: NEW[ViewerClasses.ViewerClassRec _ [flavor: $FingerPicture, paint: PaintPicture]]]; Commander.Register[key: "FingerTool", proc: MakeFingerTool, doc: "Create a finger tool." ]; END.