DIRECTORY Atom, BasicTime, FS, GGError, GGEvent, GGInterfaceTypes, GGBasicTypes, GGParseIn, GGSessionLog, GGUserInput, GGWindow, IO, Rope, SlackProcess; GGSessionLogImpl: CEDAR PROGRAM IMPORTS Atom, BasicTime, FS, GGError, GGEvent, GGParseIn, GGUserInput, GGWindow, IO, Rope, SlackProcess EXPORTS GGSessionLog = BEGIN CharClass: TYPE = IO.CharClass; GargoyleData: TYPE = GGInterfaceTypes.GargoyleData; Point: TYPE = GGBasicTypes.Point; OpenSessionLog: PUBLIC PROC [fileName: Rope.ROPE, gargoyleData: GargoyleData] = { stream: IO.STREAM; fullName: Rope.ROPE; gravExtent: REAL; IF gargoyleData.debug.logStream#NIL THEN { GGError.Append[gargoyleData.feedback, "Closing existing script.", oneLiner]; gargoyleData.debug.logStream.Close[]; }; IF Rope.Length[fileName] = 0 THEN GOTO NoName; [fullName,,] _ FS.ExpandName[fileName, gargoyleData.currentWDir ! FS.Error => GOTO BadName;]; stream _ FS.StreamOpen[fullName, $create ! FS.Error => GOTO FSError]; gargoyleData.debug.logFileName _ fullName; gargoyleData.debug.logStream _ stream; GGEvent.InitializeAlignments[NIL, gargoyleData]; GGError.Append[gargoyleData.feedback, Rope.Cat["Opened ", fullName, " for scripting"], oneLiner]; gravExtent _ GGWindow.GetGravityExtent[gargoyleData]; EnterAction[[0,0], LIST[$SetGravityExtent, NEW[REAL _ gravExtent]], FALSE, gargoyleData]; EXITS NoName => {GGError.Append[gargoyleData.feedback, "Please select a filename for scripting", oneLiner]; GGError.Blink[gargoyleData.feedback];}; BadName => {GGError.Append[gargoyleData.feedback, Rope.Concat["Illegal Name: ", fileName], oneLiner]; GGError.Blink[gargoyleData.feedback];}; FSError => {GGError.Append[gargoyleData.feedback, Rope.Concat["FSError while trying ", fileName], oneLiner]; GGError.Blink[gargoyleData.feedback];}; }; CloseSessionLog: PUBLIC PROC [gargoyleData: GargoyleData] = { IF gargoyleData.debug.logStream=NIL THEN GOTO NotLogging; gargoyleData.debug.logStream.Close[]; GGError.Append[gargoyleData.feedback, Rope.Concat["Closed ", gargoyleData.debug.logFileName], oneLiner]; gargoyleData.debug.logStream _ NIL; gargoyleData.debug.logFileName _ NIL; EXITS NotLogging => GGError.Append[gargoyleData.feedback, "Not scripting this session", oneLiner]; }; EnterAction: PUBLIC SlackProcess.LoggingProc = { gargoyleData: GargoyleData _ NARROW[clientData]; stream: IO.STREAM _ gargoyleData.debug.logStream; IF stream=NIL THEN { GGError.AppendHerald[gargoyleData.feedback, "Attempted script entry without open script file", oneLiner]; GGError.Blink[gargoyleData.feedback]; RETURN; }; IF mouseEvent THEN stream.PutF["* [%g, %g] ", [real[point.x]], [real[point.y]] ]; FOR actionList: LIST OF REF ANY _ action, actionList.rest UNTIL actionList = NIL DO WITH actionList.first SELECT FROM atom: ATOM => { IF atom = $SawStartOp OR atom = $SawSelectAll OR atom = $SawTextFinish THEN LOOP; stream.PutF["%g", [rope[Atom.GetPName[atom]]]]; }; n: REF INT => stream.PutF["%g", [integer[n^]]]; r: REF REAL => stream.PutF["%g", [real[r^]]]; c: REF CHAR => stream.PutF["'%g", [character[c^]]]; rope: Rope.ROPE => stream.PutF["\"%g\"", [rope[rope]]]; ENDCASE => ERROR; IF actionList.rest = NIL THEN stream.PutChar[IO.CR] ELSE stream.PutChar[IO.SP]; ENDLOOP; }; PlaybackFromFile: PUBLIC PROC [fileName: Rope.ROPE, gargoyleData: GargoyleData] = { fullName: Rope.ROPE; success: BOOL; f: IO.STREAM; startTime: BasicTime.GMT; startTimeCard: CARD; success _ TRUE; [fullName,,] _ FS.ExpandName[fileName, gargoyleData.currentWDir ! FS.Error => IF error.group = user THEN { success _ FALSE; CONTINUE; } ]; IF NOT success THEN { GGError.Append[gargoyleData.feedback, Rope.Cat["Could not open ", fileName, " for playback"], oneLiner]; GGError.Blink[gargoyleData.feedback]; RETURN; }; [f, success] _ OpenExistingFile[fullName, gargoyleData]; IF NOT success THEN { GGError.Append[gargoyleData.feedback, Rope.Cat["Could not open ", fullName, " for playback"], oneLiner]; GGError.Blink[gargoyleData.feedback]; RETURN; }; GGEvent.InitializeAlignments[NIL, gargoyleData]; gargoyleData.aborted[playback] _ FALSE; -- just in case there was one from last playback startTime _ BasicTime.Now[]; startTimeCard _ BasicTime.ToNSTime[startTime]; WHILE success DO success _ PlayAction[f, gargoyleData]; IF gargoyleData.aborted[playback] THEN { GGError.Append[gargoyleData.feedback, Rope.Cat["Aborted playback of ", fullName], oneLiner]; SlackProcess.FlushQueue[gargoyleData.slackHandle]; gargoyleData.refresh.suppressRefresh _ FALSE; -- in case you killed FastPlayback gargoyleData.aborted[playback] _ FALSE; RETURN; }; ENDLOOP; GGUserInput.PlayAction[[0,0], LIST[$EndOfSessionLogMessage, fullName, NEW[CARD _ startTimeCard]], FALSE, gargoyleData]; gargoyleData.aborted[playback] _ FALSE; }; EndOfSessionLogMessage: PUBLIC PROC [event: LIST OF REF ANY, clientData: REF ANY] = { gargoyleData: GargoyleData _ NARROW[clientData]; logName: Rope.ROPE _ NARROW[event.rest.first]; startTimeCard: CARD _ NARROW[event.rest.rest.first, REF CARD]^; startTime: BasicTime.GMT _ BasicTime.FromNSTime[startTimeCard]; endTime: BasicTime.GMT; totalTime: INT; endTime _ BasicTime.Now[]; totalTime _ BasicTime.Period[startTime, endTime]; GGError.PutF[gargoyleData.feedback, oneLiner, "Finished playback of %g in time (%r)", [rope[logName]], [integer[totalTime]]]; }; OpenExistingFile: PROC [name: Rope.ROPE, gargoyleData: GargoyleData] RETURNS [f: IO.STREAM, success: BOOL] = { success _ TRUE; f _ FS.StreamOpen[name ! FS.Error => { IF error.group = user THEN { success _ FALSE; GGError.Append[gargoyleData.feedback, error.explanation, oneLiner]; GGError.Blink[gargoyleData.feedback]; } ELSE ERROR; CONTINUE}]; }; AppendAtom: PROC [atom: ATOM, list: LIST OF REF ANY] RETURNS [newList: LIST OF REF ANY] = { l: LIST OF REF ANY _ list; IF l = NIL THEN RETURN[LIST[atom]]; UNTIL l.rest = NIL DO l _ l.rest ENDLOOP; l.rest _ CONS[atom, NIL]; newList _ list; }; AppendToken: PROC [token: REF ANY, list: LIST OF REF ANY] RETURNS [newList: LIST OF REF ANY] = { l: LIST OF REF ANY _ list; IF l = NIL THEN RETURN[LIST[token]]; UNTIL l.rest = NIL DO l _ l.rest ENDLOOP; l.rest _ CONS[token, NIL]; newList _ list; }; PlayAction: PROC [f: IO.STREAM, gargoyleData: GargoyleData] RETURNS [success: BOOL] = { c: CHAR; mouseEvent: BOOL; point: Point; good: BOOL; token: REF ANY; action: LIST OF REF ANY; success _ TRUE; c _ IO.GetChar[f !IO.EndOfStream => {success _ FALSE; CONTINUE}]; IF NOT success THEN RETURN; IF c = '* THEN { good _ GGParseIn.ReadHorizontalBlank[f]; IF NOT good THEN ERROR; point _ GGParseIn.ReadPoint[f]; mouseEvent _ TRUE } ELSE { mouseEvent _ FALSE; IO.Backup[f, c]; }; token _ $Start; UNTIL token = $EndOfLine DO [token] _ ReadSpacesAndToken[f]; IF token = $EndOfLine THEN LOOP; action _ AppendToken[token, action] ENDLOOP; GGUserInput.PlayAction[point, action, mouseEvent, gargoyleData]; }; IsDigitOrOp: PROC [c: CHAR] RETURNS [BOOL] = { RETURN[c IN ['0..'9] OR c = '- OR c = '+]; }; ReadSpacesAndToken: PUBLIC PROC [f: IO.STREAM] RETURNS [token: REF ANY] = { word: Rope.ROPE; good: BOOL; int: INT; real: REAL; firstChar: CHAR; good _ GGParseIn.ReadHorizontalBlank[f]; IF NOT good THEN { token _ $EndOfLine; RETURN; }; firstChar _ IO.PeekChar[f]; SELECT TRUE FROM firstChar = '" => { token _ f.GetRopeLiteral[]; }; firstChar = '' => { [] _ f.GetChar[]; token _ NEW[CHAR _ f.GetChar[]]; }; IsDigitOrOp[firstChar] => { word _ GGParseIn.ReadBlankAndWord[f]; IF Rope.Find[word, "."] = -1 THEN { -- an integer int _ IO.GetInt[IO.RIS[word]]; token _ NEW[INT _ int]; } ELSE { real _ IO.GetReal[IO.RIS[word]]; token _ NEW[REAL _ real]; }; }; ENDCASE => { word _ GGParseIn.ReadBlankAndWord[f]; token _ Atom.MakeAtom[word]; }; }; FailedReadSpacesAndToken: PUBLIC PROC [f: IO.STREAM] RETURNS [token: REF ANY] = { word: Rope.ROPE; good: BOOL; int: INT; real: REAL; tokenKind: IO.TokenKind; good _ GGParseIn.ReadHorizontalBlank[f]; IF NOT good THEN { token _ $EndOfLine; RETURN; }; [tokenKind, word, ----] _ IO.GetCedarTokenRope[f]; SELECT tokenKind FROM tokenDECIMAL => { int _ IO.GetInt[IO.RIS[word]]; token _ NEW[INT _ int]; }; tokenREAL => { real _ IO.GetReal[IO.RIS[word]]; token _ NEW[REAL _ real]; }; tokenCHAR => { token _ NEW[CHAR _ Rope.Fetch[word, 1]]; }; tokenROPE => token _ word; tokenID => token _ Atom.MakeAtom[word]; ENDCASE => ERROR; }; ReadLine: PUBLIC PROC [f: IO.STREAM] RETURNS [line: Rope.ROPE] = { LineBreakProc: SAFE PROC [char: CHAR] RETURNS [IO.CharClass] = CHECKED { SELECT char FROM IO.CR =>RETURN [break]; ENDCASE => RETURN [other]; }; end: BOOL _ FALSE; [line, ----] _ IO.GetTokenRope[f, LineBreakProc !IO.EndOfStream => {end _ TRUE; CONTINUE}]; IF end THEN {line _ NIL; RETURN}; }; ReadSpaceAndChar: PUBLIC PROC [f: IO.STREAM] RETURNS [token: REF ANY] = { c: CHAR; good: BOOL; c _ IO.GetChar[f !IO.EndOfStream => ERROR]; c _ IO.GetChar[f !IO.EndOfStream => ERROR]; good _ GGParseIn.ReadHorizontalBlank[f]; IF good THEN ERROR; token _ NEW[CHAR _ c]; }; END. ˜GGSessionLogImpl.mesa Last edited by Bier on January 26, 1987 4:10:24 pm PST. Contents: Routines for saving TIP Table atoms in a file to aid in interface evaluation and for playback. Pier, October 21, 1986 3:08:46 pm PDT this PROC sets up for logging but does not start it. Client must also call SlackProcess.EnableSessionLogging to start logging events. LoggingProc: TYPE = PROC [point: Point, action: LIST OF REF ANY, mouseEvent: BOOL, clientData: REF ANY]; N.B.: YOU MAY NOT EXECUTE THIS CODE EVEN THOUGH YOU ABORT A PLAYBACK. REASON: the slack queue. This proc can complete long before anything actually happens because of the queue, so you have to handle aborts at the abort detector. This code only gets executed if you abort while the queue is backed up. Two possiblilities 1) File doesn't exist. Print error message. 2) File does exist. File it in. Succeed. Reads a rope UNTIL is encountered. Κ W˜code™K™7šœi™iK™%——K˜šΟk ˜ Kšœœdœ˜Ž—K˜šœœ˜Kšœœ6œ˜hKšœ˜—K˜Kšœ œœ ˜Kšœœ!˜3Kšœœ˜!K˜šΟnœ œœ!˜QKšœ…™…Kšœœœ˜Kšœœ˜Kšœ œ˜šœœœ˜*KšœL˜LKšœ%˜%K˜—Kšœœœ˜.Kšœœ1œ œ ˜]Kšœ œ œ œ ˜EKšœ*˜*Kšœ&˜&Kšœœ˜0Kšœa˜aKšœ5˜5Kš œœœœœ˜Yš˜Kšœ˜Kšœ˜Kšœ”˜”—Kšœ˜K˜—šžœ œ!˜=Kšœœœœ ˜9Kšœ%˜%Kšœh˜hKšœœ˜#Kšœ!œ˜%š˜Kšœ\˜\—K˜K˜—šž œœ˜0Kšœ œœœœœœœœœ™hKšœœ ˜0Kšœœœ ˜1šœœœ˜Kšœi˜iKšœ%˜%Kšœ˜K˜—Kšœ œ?˜Qšœ œœœœœœ˜SKšœœ˜!šœœ˜Kšœœ/œœ˜QKšœ/˜/K˜—Kšœœœ%˜/Kšœœœ"˜-Kšœœœ(˜3Kšœ œ(˜7Kšœœ˜Kš œœœœœ˜3Kšœœœ˜—Kšœ˜K˜K˜—šžœœœœ!˜SKšœœ˜Kšœ œ˜Kšœœœ˜ Kšœœ˜Kšœœ˜Kšœ œ˜šœœ.˜?šœœ œœ˜*Kšœ œ˜Kšœ˜ K˜—Kšœ˜—šœœ œ˜Kšœh˜hKšœ%˜%Kšœ˜Kšœ˜—Kšœ8˜8šœœ œ˜Kšœh˜hKšœ%˜%Kšœ˜Kšœ˜—Kšœœ˜0Kšœ!œΟc0˜XKšΟb˜Kšœ.˜.šœ ˜Kšœ   œ˜&šœ œ˜(Kš E™EKš œγ™ιKšœ\˜\Kšœ2˜2Kšœ'œŸ"˜PKšœ!œ˜'Kšœ˜K˜—Kšœ˜—Kš œœ$œœœ˜wKšœ!œ˜'Kšœ˜—K˜šžœœœ œœœœœœ˜UKšœœ ˜0Kšœœœ˜.Kš œœœœœ˜?Kšœœ'˜?Kšœœ˜Kšœ œ˜Kš ˜Kšœ1˜1Kšœ}˜}K˜K˜—šžœœ œœœœ œ˜nKšœ œ˜šœ™Kšœ-™-Kšœ+™+—šœœ˜šœœ ˜šœœ˜Kšœ œ˜KšœC˜CKšœ%˜%Kšœ˜—Kšœœ˜ Kšœ˜ ——K˜K˜—šž œœœœœœœœ œœœœ˜[Kš œœœœœ˜Kš œœœœœ˜#Kšœ œœ œ˜)Kšœ œœ˜Kšœ˜K˜K˜—šž œœ œœœœœœœ œœœœ˜`Kš œœœœœ˜Kš œœœœœ ˜$Kšœ œœ œ˜)Kšœ œœ˜Kšœ˜K˜K˜—š ž œœœœœ œ˜WKšœœ˜Kšœ œ˜K˜ Kšœœ˜ Kšœœœ˜Kš œœœœœ˜Kšœ œ˜šœœ ˜Kšœœœœ˜0—Kšœœ œœ˜šœœ˜K˜(Kšœœœœ˜K˜Kšœ ˜K˜—šœ˜Kšœ œ˜Kšœ˜K˜—K˜šœ˜K˜ Kšœœœ˜ Kšœ#˜#—Kšœ˜Kšœ@˜@K˜K˜—K˜š ž œœœœœ˜.Kšœœ œœ ˜*K˜K˜—šžœœœœœœ œœ˜KKšœ œ˜Kšœœ˜ Kšœœ˜ Kšœœ˜ Kšœ œ˜Kšœ(˜(šœœœ˜Kšœ˜Kšœ˜K˜—Kšœ œ ˜Kšœœ˜šœœ˜Kšœ  œ˜K˜—šœœ˜K˜Kšœœœ˜ K˜—šœ˜Kšœ œ˜%šœœŸ ˜1Kšœœœœ˜Kšœœœ˜K˜—šœ˜Kšœœ œœ˜ Kšœœœ ˜K˜—K˜—šœ˜ Kšœ œ˜%Kšœ˜K˜—Kšœ˜K˜—šžœœœœœœ œœ˜QKšœ œ˜Kšœœ˜ Kšœœ˜ Kšœœ˜ Kšœ œ ˜Kšœ(˜(šœœœ˜Kšœ˜Kšœ˜K˜—KšœŸœœ˜2šœ ˜šœ˜Kšœœœœ˜Kšœœœ˜K˜—šœ˜Kšœœ œœ˜ Kšœœœ ˜K˜—šœ˜Kšœœœ˜(Kšœ˜—Kšœ˜Kšœ'˜'Kšœœ˜—Kšœ˜K˜—procšžœœœœœœ œ˜BLšœ'™'šž œœœœœœœ˜Hšœ˜Lšœœœ ˜Lšœœ ˜—Lšœ˜—Lšœœœ˜šœŸœœ˜/Lšœœœœ˜+—Lšœœ œœ˜!Lšœ˜L˜—šžœœœœœœ œœ˜IKšœœ˜Kšœœ˜ Kšœœ œœ˜+Kšœœ œœ˜+Kšœ(˜(Kšœœœ˜Kšœœœ˜Kšœ˜K˜K˜—K˜Kšœ˜K˜K˜K˜—…—"Ό3«