GGUIUtilityImpl.mesa
Contents: Utility routines for the user interface level of Gargoyle.
Copyright Ó 1988, 1989 by Xerox Corporation. All rights reserved.
Bier, August 30, 1991 4:08 pm PDT
Pier, March 29, 1990 10:44 am PST
Doug Wyatt, September 14, 1989 4:27:53 pm PDT
DIRECTORY
Atom, BasicTime, BiScrollers, Convert, EBMesaLisp, Feedback, FeedbackTypes, FileNames, FS, GGBasicTypes, GGCaret, GGContainer, GGControlPanelTypes, GGDescribe, GGEvent, GGFileOps, GGFont, GGHistory, GGHistoryTypes, GGInterfaceTypes, GGModelTypes, GGRefreshTypes, GGSegmentTypes, GGSessionLog, GGSliceOps, GGState, GGUIUtility, GGUserInput, GGUtility, ImagerColor, IO, MJSContainers, MultiCursors, Rope, SessionLog, SlackProcess, ViewerClasses;
GGUIUtilityImpl: CEDAR PROGRAM
IMPORTS Atom, BasicTime, BiScrollers, Convert, EBMesaLisp, Feedback, FileNames, FS, GGCaret, GGDescribe, GGEvent, GGFileOps, GGFont, GGSessionLog, GGSliceOps, GGState, GGUIUtility, GGUserInput, GGUtility, IO, MJSContainers, Rope, SessionLog, SlackProcess
EXPORTS GGContainer, GGHistoryTypes, GGInterfaceTypes, GGSessionLog, GGUIUtility =
BEGIN
Change: PUBLIC TYPE = GGHistory.Change; -- exported to GGHistoryTypes
ControlsObj: PUBLIC TYPE = GGControlPanelTypes.ControlsObj; -- exported to GGInterfaceTypes
HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent;
Point: TYPE = GGBasicTypes.Point;
Vector: TYPE = GGBasicTypes.Vector;
AlignmentPoint: TYPE = GGInterfaceTypes.AlignmentPoint;
DisplayStyle: TYPE = GGModelTypes.DisplayStyle;
MsgRouter: TYPE = FeedbackTypes.MsgRouter;
FeatureData: TYPE = GGModelTypes.FeatureData;
FontData: TYPE = GGModelTypes.FontData;
GGData: TYPE = GGInterfaceTypes.GGData;
GravityType: TYPE = GGInterfaceTypes.GravityType;
RefreshDataObj: PUBLIC TYPE = GGRefreshTypes.RefreshDataObj;
Segment: TYPE = GGSegmentTypes.Segment;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
Traj: TYPE = GGModelTypes.Traj;
TrajData: TYPE = GGModelTypes.TrajData;
Viewer: TYPE = ViewerClasses.Viewer;
UnlinkError: SIGNAL = CODE;
GGDescribe
DescribeFeature: PUBLIC PROC [feature: FeatureData, hitData: REF ANY, ggData: GGData] RETURNS [rope: Rope.ROPE] = {
IF feature = NIL THEN RETURN["nothing"]
ELSE {
SELECT feature.type FROM
slice => {
slice: Slice ← NARROW[feature.shape, SliceDescriptor].slice;
rope ← GGSliceOps.DescribeHit[slice, hitData];
};
distanceLine => rope ← "distance line";
slopeLine => rope ← "slope line";
angleLine => rope ← "angle line";
symmetryLine => rope ← "symmetry line";
radiiCircle => rope ← "compass circle";
intersectionPoint => {
firstObj, secondObj: Rope.ROPE;
alignPoint: AlignmentPoint ← NARROW[feature.shape];
line1: FeatureData ← alignPoint.curve1;
line2: FeatureData ← alignPoint.curve2;
tangent: BOOL ← alignPoint.tangent;
IF line1 = NIL AND line2 = NIL THEN {
rope ← IO.PutFR["the anchor"];
}
ELSE {
firstObj ← IF line1 # NIL THEN DescribeSourceFeature[line1, ggData]
ELSE "unknown";
secondObj ← IF line2 # NIL THEN DescribeSourceFeature[line2, ggData]
ELSE "unknown";
IF tangent THEN rope ← IO.PutFR["a %g/%g tangency point", [rope[firstObj]], [rope[secondObj]] ]
ELSE rope ← IO.PutFR["a %g/%g intersection point", [rope[firstObj]], [rope[secondObj]] ];
};
};
midpoint => {
alignPoint: AlignmentPoint ← NARROW[feature.shape];
curveFeature: FeatureData ← alignPoint.curve1;
SELECT curveFeature.type FROM
slice => {
slice: Slice ← NARROW[curveFeature.shape, SliceDescriptor].slice;
rope ← GGSliceOps.DescribeHit[slice, hitData];
};
ENDCASE => rope ← "unknown";
rope ← IO.PutFR["midpoint of %g", [rope[rope]]];
};
anchor => {
rope ← IO.PutFR["anchor"];
};
ENDCASE => ERROR;
};
};
DescribeSourceFeature: PUBLIC PROC [feature: FeatureData, ggData: GGData] RETURNS [rope: Rope.ROPE] = {
IF feature = NIL THEN RETURN["nothing"]
ELSE {
SELECT feature.type FROM
slice => rope ← Rope.Concat[Atom.GetPName[GGSliceOps.GetType[NARROW[feature.shape, SliceDescriptor].slice]], " slice"];
distanceLine => rope ← "distance line";
slopeLine => rope ← "slope line";
angleLine => rope ← "angle line";
symmetryLine => rope ← "symmetry line";
radiiCircle => rope ← "compass circle";
intersectionPoint => {
firstObj, secondObj: Rope.ROPE;
firstObj ← IF NARROW[feature.shape, AlignmentPoint].curve1 # NIL THEN DescribeSourceFeature[NARROW[feature.shape, AlignmentPoint].curve1, ggData]
ELSE "unknown";
secondObj ← IF NARROW[feature.shape, AlignmentPoint].curve2 # NIL THEN DescribeSourceFeature[NARROW[feature.shape, AlignmentPoint].curve2, ggData]
ELSE "unknown";
rope ← IO.PutFR["a %g/%g intersection point", [rope[firstObj]], [rope[secondObj]] ];
};
midpoint => rope ← IO.PutFR["midpoint of segment ???"];
ENDCASE => ERROR;
};
};
GravityTypeToRope: PUBLIC PROC [gravityType: GravityType] RETURNS [rope: Rope.ROPE] = {
rope ← SELECT gravityType FROM
pointsPreferred => "pointsPreferred",
facesPreferred => "facesPreferred",
linesPreferred => "linesPreferred",
ENDCASE => ERROR
};
GravityTypeFromRope: PUBLIC PROC [rope: Rope.ROPE] RETURNS [gravityType: GravityType] = {
gravityType ← SELECT TRUE FROM
Rope.Equal[rope, "pointsPreferred", FALSE] => pointsPreferred,
Rope.Equal[rope, "facesPreferred", FALSE] => facesPreferred,
Rope.Equal[rope, "linesPreferred", FALSE] => linesPreferred,
ENDCASE => ERROR;
};
DisplayStyleToRope: PUBLIC PROC [displayStyle: DisplayStyle] RETURNS [rope: Rope.ROPE] = {
rope ← IF displayStyle=screen THEN "screen" ELSE "print";
};
DisplayStyleFromRope: PUBLIC PROC [rope: Rope.ROPE] RETURNS [displayStyle: DisplayStyle] = {
displayStyle ← SELECT TRUE FROM
Rope.Equal[rope, "screen", FALSE] => screen,
Rope.Equal[rope, "print", FALSE] => print,
ENDCASE => ERROR;
};
GGContainer
GGContainerCreate: PUBLIC PROC [info: ViewerClasses.ViewerRec ← [], paint: BOOLTRUE] RETURNS [gargoyleContainer: MJSContainers.MJSContainer] = {
Creates a new, empty container. You probably want to pass a name in the info record.
RETURN[MJSContainers.Create[$GargoyleMJSContainer, info, paint]];
};
ChildYBound: PUBLIC PROC [gargoyleContainer: MJSContainers.MJSContainer, child: Viewer] = {
constrain (child.wy + child.wh = gargoyleContainer.wh)
MJSContainers.ChildYBound[gargoyleContainer, child];
};
ChildXBound: PUBLIC PROC [gargoyleContainer: MJSContainers.MJSContainer, child: Viewer] = {
constrain (child.wx + child.ww = gargoyleContainer.ww)
after next time gargoyleContainer is painted
MJSContainers.ChildXBound[gargoyleContainer, child];
};
GargoyleContainerSave: PUBLIC ViewerClasses.SaveProc = {
[self: ViewerClasses.Viewer, force: BOOLFALSE]
This SaveProc is called only by the emergency save mechanism, either directly or via GGWindowImpl.BSSaveProc. Normal saves call Store in GGEvent.
ggData: GGData;
fullName: Rope.ROPE;
IF BiScrollers.ViewerIsABiScroller[self] THEN { -- emergency save called through picture
ggData ← NARROW[BiScrollers.ClientDataOfViewer[self]];
fullName ← IF ggData.controls.picture.file=NIL THEN Rope.Cat[FileNames.CurrentWorkingDirectory[], "SaveAllEdits-", Convert.RopeFromInt[from: (emergencyIndex ← emergencyIndex+1), base: 10, showRadix: FALSE], ".gargoyle"] ELSE ggData.controls.picture.file;
}
ELSE { -- emergency save called through panel
ggData ← NARROW[MJSContainers.GetClientData[self]];
fullName ← IF ggData.controls.panel.file=NIL THEN Rope.Cat[FileNames.CurrentWorkingDirectory[], "SaveAllEdits-", Convert.RopeFromInt[from: (emergencyIndex ← emergencyIndex+1), base: 10, showRadix: FALSE], ".gargoyle"] ELSE ggData.controls.panel.file;
};
GGEvent.Store[ggData, LIST[$Emergency, FileNames.StripVersionNumber[fullName]]]; -- save the action area
};
GargoyleContainerDestroy: PUBLIC ViewerClasses.DestroyProc = {
ggData: GGData ← NARROW[MJSContainers.GetClientData[self]];
Close all Playback Scripts.
IF ggData.debug.autoScriptStream#NIL THEN [ggData.debug.autoScriptStream, ggData.debug.autoScriptName] ← GGSessionLog.CloseScript[ggData.debug.autoScriptStream, ggData.debug.autoScriptName, ggData.router];
IF ggData.debug.writeScriptStream#NIL THEN [ggData.debug.writeScriptStream, ggData.debug.writeScriptName] ← GGSessionLog.CloseScript[ggData.debug.writeScriptStream, ggData.debug.writeScriptName, ggData.router];
if there is only one viewer or there is a separate picture viewer that has been destroyed, then destroy the control panel and free the scene storage. If the separate picture is not yet destroyed, don't free the scene storage so that an Emergency save can save the scene.
IF ggData.controls.topper=ggData.controls.panel OR ggData.controls.picture.destroyed THEN GGUserInput.EventNotify[ggData, LIST[$Destroy]]; -- frees much garbage
};
GargoyleContainerSet: PUBLIC ViewerClasses.SetProc = {
SetProc: TYPE = PROC [self: Viewer, data: REF ANY, finalise: BOOLTRUE, op: ATOMNIL];
child: Viewer;
ggData: GGData ← NARROW[MJSContainers.GetClientData[self]];
IF data=NIL THEN RETURN; -- November 27, 1985 KAP
child ← NARROW[data];
IF op=$YBound THEN ChildYBound[self, child]
ELSE IF op=$XBound THEN ChildXBound[self, child]
ELSE ERROR;
};
emergencyIndex: INT ← 0;
GGSessionLog
OpenScript: PUBLIC PROC [fileName: Rope.ROPE, ggData: GGData, oldStream: IO.STREAMNIL, oldScriptName: Rope.ROPE ← NIL] RETURNS [stream: IO.STREAM, fullName: Rope.ROPE] = {
this PROC sets up for logging but does not start it. Client must also call SlackProcess.EnableSessionLogging to start logging events.
success: BOOLFALSE;
IF oldStream#NIL THEN [----, ----] ← CloseScript[oldStream, oldScriptName, ggData.router];
[fullName, success] ← GGFileOps.GetScriptFileName["OpenScript", FileNames.StripVersionNumber[fileName], ggData.currentWDir, ggData.router];
IF NOT success THEN RETURN;
stream ← FS.StreamOpen[fullName, $create ! FS.Error => GOTO FSError];
GGEvent.InitializeAlignments[ggData, LIST[$OpenScript]];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "OpenScript: %g opened", [rope[fullName]] ];
Header
ActionToScript[stream, LIST[$Version, NEW[REAL ← GGUtility.version]]];
CaptureSessionState[stream, ggData];
EXITS
FSError => Feedback.Append[ggData.router, oneLiner, $Complaint, Rope.Concat["FSError while trying ", fileName] ];
};
AppendScriptInternal: PROC [fileName: Rope.ROPE, router: MsgRouter, wdir: Rope.ROPENIL] RETURNS [stream: IO.STREAM, fullName: Rope.ROPE] = {
success: BOOLFALSE;
[fullName, success] ← GGFileOps.GetScriptFileName["AppendScript", fileName, wdir, router];
IF NOT success THEN RETURN;
stream ← FS.StreamOpen[fullName, $append ! FS.Error => GOTO FSError];
EXITS
FSError => Feedback.Append[router, oneLiner, $Complaint, Rope.Concat["AppendScript failed: FS Error while trying to open ", fileName] ];
};
AppendScript: PUBLIC PROC [fileName: Rope.ROPE, ggData: GGData, oldStream: IO.STREAMNIL, oldScriptName: Rope.ROPE] RETURNS [stream: IO.STREAM, fullName: Rope.ROPE] = {
success: BOOLFALSE;
IF oldStream#NIL THEN [----, ----] ← CloseScript[oldStream, oldScriptName, ggData.router];
[fullName, success] ← GGFileOps.GetScriptFileName["AppendScript", fileName, ggData.currentWDir, ggData.router];
IF NOT success THEN RETURN;
stream ← FS.StreamOpen[fullName, $append ! FS.Error => GOTO FSError];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "AppendToScript: %g opened for appending", [rope[fullName]] ];
EXITS
FSError => Feedback.Append[ggData.router, oneLiner, $Complaint, Rope.Concat["AppendScript failed: FS Error while trying to open ", fileName] ];
};
CaptureSessionState: PROC [stream: IO.STREAM, ggData: GGData] = {
gravExtent, scaleUnit: REAL;
showColors: BOOL;
gravityOn, midpointsOn, heuristicsOn, showAlignments: BOOL;
gravityType: GravityType;
defaultFont: FontData;
displayStyle: GGModelTypes.DisplayStyle;
caretPoint: Point;
caretNormal: Vector;
strokeColor, fillColor: ImagerColor.Color;
values: LIST OF REAL;
on: LIST OF BOOL;
names: LIST OF Rope.ROPE;
transformRope: Rope.ROPE;
BiScrollers
transformRope ← GGDescribe.FactoredTransformationToRope[GGState.GetBiScrollersTransform[ggData]];
ActionToScript[stream, LIST[$SetBiScrollersTransform, transformRope]];
Alignments
[values, on] ← GGState.GetSlopeAlignments[ggData];
ActionToScript[stream, LIST[$SetSlopes, GGDescribe.ScalarButtonValuesToRope[NIL, values, on]]];
[values, on] ← GGState.GetAngleAlignments[ggData];
ActionToScript[stream, LIST[$SetAngles, GGDescribe.ScalarButtonValuesToRope[NIL, values, on]]];
[names, values, on] ← GGState.GetRadiusAlignments[ggData];
ActionToScript[stream, LIST[$SetRadii, GGDescribe.ScalarButtonValuesToRope[names, values, on]]];
[names, values, on] ← GGState.GetLineDistanceAlignments[ggData];
ActionToScript[stream, LIST[$SetDistances, GGDescribe.ScalarButtonValuesToRope[names, values, on]]];
midpointsOn ← GGState.GetMidpoints[ggData];
ActionToScript[stream, LIST[$SetMidpoints, IF midpointsOn THEN "T" ELSE "F"]];
heuristicsOn ← GGState.GetHeuristics[ggData];
ActionToScript[stream, LIST[$SetHeuristics, IF heuristicsOn THEN "T" ELSE "F"]];
showAlignments ← GGState.GetShowAlignments[ggData];
ActionToScript[stream, LIST[$SetShowAlignments, IF showAlignments THEN "T" ELSE "F"]];
showColors ← GGState.GetShowColors[ggData];
ActionToScript[stream, LIST[$SetShowColors, IF showColors THEN "T" ELSE "F"]];
scaleUnit ← GGState.GetScaleUnit[ggData];
ActionToScript[stream, LIST[$SetScaleUnit, NEW[REAL ← scaleUnit], $Quietly]];
displayStyle ← GGState.GetDisplayStyle[ggData];
ActionToScript[stream, LIST[$SetDisplayStyle, GGUIUtility.DisplayStyleToRope[displayStyle]]];
gravityOn ← GGState.GetGravity[ggData];
ActionToScript[stream, LIST[$SetGravity, IF gravityOn THEN "T" ELSE "F"]];
gravExtent ← GGState.GetGravityExtent[ggData];
ActionToScript[stream, LIST[$SetGravityExtent, NEW[REAL ← gravExtent]]];
gravityType ← GGState.GetGravityType[ggData];
ActionToScript[stream, LIST[$SetGravityChoice, GGUIUtility.GravityTypeToRope[gravityType]] ];
defaultFont ← GGState.GetDefaultFont[ggData];
ActionToScript[stream, LIST[$SetDefaultFont, GGFont.FontAsLiteralRope[defaultFont]] ];
strokeColor ← GGState.GetDefaultStrokeColor[ggData];
ActionToScript[stream, LIST[$SetDefaultLineColorFromRope, GGDescribe.ColorToRope[strokeColor]] ];
Default Stroke Properties go Here
fillColor ← GGState.GetDefaultFillColor[ggData];
ActionToScript[stream, LIST[$SetDefaultFillColorFromRope, GGDescribe.ColorToRope[fillColor]] ];
Default Dashing goes Here
Default Shadows go Here
Default Anchor goes Here
caretPoint ← GGCaret.GetPoint[ggData.caret];
ActionToScript[stream, LIST[$SetCaretPosition, NEW[Point ← caretPoint]] ];
caretNormal ← GGCaret.GetNormal[ggData.caret];
ActionToScript[stream, LIST[$SetCaretNormal, NEW[Vector ← caretNormal]] ];
};
CloseScript: PUBLIC PROC [stream: IO.STREAM, scriptName: Rope.ROPE, router: MsgRouter] RETURNS [newStream: IO.STREAM, newName: Rope.ROPE] = {
IF stream=NIL THEN GOTO NotLogging;
stream.Close[];
Feedback.PutF[router, oneLiner, $Feedback, "CloseScript: %g closed", [rope[scriptName]] ];
newStream ← NIL;
newName ← NIL;
EXITS
NotLogging => Feedback.Append[router, oneLiner, $Feedback, "Not scripting this session"];
};
FlushScript: PUBLIC PROC [oldStream: IO.STREAM, oldScriptName: Rope.ROPE, router: MsgRouter] RETURNS [newStream: IO.STREAM, newName: Rope.ROPE] = {
Close the script, causing all actions to be written to disk. Then, open it again with a new version number.
name: Rope.ROPE;
IF oldStream # NIL THEN oldStream.Close[];
name ← FileNames.StripVersionNumber[oldScriptName];
[newStream, newName] ← AppendScriptInternal[name, router];
Feedback.Append[router, oneLiner, $Feedback, "Autoscript checkpoint"];
};
EnterAction: PUBLIC PROC [clientData: REF ANY, inputAction: REF] = {
event: LIST OF REF ANYNARROW[inputAction];
ggData: GGData ← NARROW[clientData];
ActionToScript[ggData.debug.writeScriptStream, event];
ActionToScript[ggData.debug.autoScriptStream, event];
};
ActionToScript: PROC [stream: IO.STREAM, event: LIST OF REF ANY] = {
IF stream=NIL THEN RETURN;
IF event.first = $SawStartOp OR event.first = $SawSelectAll OR event.first = $SawTextFinish OR event.first = $SawMouseFinish THEN RETURN;
SessionLog.EnterAction[stream, event];
};
PlayAction: PROC [clientData: REF, inputAction: REF] = {
event: LIST OF REF ANYNARROW[inputAction];
GGUserInput.PlayAction[clientData, event];
};
PlaybackFromFile: PUBLIC PROC [fileName: Rope.ROPE, ggData: GGData] = {
fullName: Rope.ROPE;
success: BOOLFALSE;
endOfStream: BOOLFALSE;
f: IO.STREAM;
startTime: BasicTime.GMT;
startTimeCard: CARD;
BEGIN
[fullName, success] ← GGFileOps.GetScriptFileName["Playback script", fileName, ggData.currentWDir, ggData.router];
IF NOT success THEN {fullName ← fileName; GOTO OpenFileProblem};
[f, success] ← OpenExistingFile[fullName, ggData];
IF NOT success THEN GOTO OpenFileProblem;
GGEvent.InitializeAlignments[ggData, LIST[$PlaybackFromFile]]; -- should only happen for old script versions *****
ggData.aborted[playback] ← FALSE; -- just in case there was one from last playback
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Playing: %g", [rope[fullName]] ];
startTime ← BasicTime.Now[];
startTimeCard ← BasicTime.ToNSTime[startTime];
WHILE NOT endOfStream DO
endOfStream ← SessionLog.PlayAction[f, ggData, PlayAction];
IF ggData.aborted[playback] THEN {
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.
Feedback.Append[ggData.router, oneLiner, $Feedback, Rope.Cat["Aborted playback of ", fullName]];
SlackProcess.FlushQueue[ggData.slackHandle];
ggData.refresh.suppressRefresh ← FALSE; -- in case you killed FastPlayback
ggData.refresh.suppressScreen ← FALSE; -- in case you killed FastPlayback
ggData.aborted[playback] ← FALSE;
RETURN;
};
ENDLOOP;
GGUserInput.PlayAction[ggData, LIST[$EndOfSessionLogMessage, fullName, NEW[CARD ← startTimeCard]]];
ggData.aborted[playback] ← FALSE;
EXITS
OpenFileProblem => Feedback.Append[ggData.router, oneLiner, $Complaint, Rope.Cat["Could not open ", fullName, " for playback"] ];
END;
};
EndOfScriptMessage: PUBLIC PROC [ggData: GGData, event: LIST OF REF ANY] = {
logName: Rope.ROPENARROW[event.rest.first];
startTimeCard: CARDIF ISTYPE[event.rest.rest.first, REF INT] THEN NARROW[event.rest.rest.first, REF INT]^ ELSE 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];
Feedback.PutF[ggData.router, oneLiner, $Statistics, "Finished playback of %g in time (%r)", [rope[logName]], [integer[totalTime]]];
};
OpenExistingFile: PROC [name: Rope.ROPE, ggData: GGData] RETURNS [f: IO.STREAM, success: BOOLFALSE] = {
Two possiblilities
1) File doesn't exist or already open. Print error message. Fail.
2) File does exist. File it in. Succeed.
success ← TRUE;
f ← FS.StreamOpen[name ! FS.Error => {
success ← FALSE;
Feedback.Append[ggData.router, oneLiner, $Complaint, error.explanation];
CONTINUE};
];
};
SafeClose: PUBLIC PROC [stream: IO.STREAM, router: FeedbackTypes.MsgRouter ← NIL] ~ {
IO.Close[stream !
IO.Error => {
msg: Rope.ROPE ~ "IO.Close failed (IO.Error). Continuing anyway";
IF router=NIL
THEN Feedback.AppendByName[$Gargoyle, oneLiner, $Complaint, msg]
ELSE Feedback.Append[router, oneLiner, $Complaint, msg];
CONTINUE;
};
FS.Error => {
msg: Rope.ROPE ~ "IO.Close failed (FS.Error: %g). Continuing anyway";
IF router=NIL
THEN Feedback.PutFByName[$Gargoyle, oneLiner, $Complaint, msg, [rope[error.explanation]]]
ELSE Feedback.PutF[router, oneLiner, $Complaint, msg, [rope[error.explanation]]];
CONTINUE;
};
];
};
GGHomeDirectory: PUBLIC PROC RETURNS [Rope.ROPE] = {
returns directory name in which help material and auxiliary files may be found
RETURN ["/CedarCommon/Gargoyle/"];
};
ParseFeedbackRope: PUBLIC PROC [rope: Rope.ROPE] RETURNS [val: REF ANY] ~ {
RETURN[EBMesaLisp.Parse[IO.RIS[rope]].val];
};
Init: PROC = {
gargoyleContainerClass: MJSContainers.MJSContainerClass ← NEW[MJSContainers.MJSContainerClassRep ← [
destroy: GargoyleContainerDestroy,
set: GargoyleContainerSet,
save: GargoyleContainerSave -- used when Shift-Shift-Swat is invoked
]];
MJSContainers.RegisterClass[$GargoyleMJSContainer, gargoyleContainerClass]; -- plug in to MJSContainers
emergencyIndex ← 0;
};
Init[];
END.