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:
BOOL ←
TRUE]
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: BOOL ← FALSE]
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: BOOL ← TRUE, op: ATOM ← NIL];
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.
STREAM ←
NIL, 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: BOOL ← FALSE;
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.
ROPE ←
NIL]
RETURNS [stream:
IO.
STREAM, fullName: Rope.
ROPE] = {
success: BOOL ← FALSE;
[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.
STREAM ←
NIL, oldScriptName: Rope.
ROPE]
RETURNS [stream:
IO.
STREAM, fullName: Rope.
ROPE] = {
success: BOOL ← FALSE;
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 ANY ← NARROW[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 ANY ← NARROW[inputAction];
GGUserInput.PlayAction[clientData, event];
};
PlaybackFromFile:
PUBLIC
PROC [fileName: Rope.
ROPE, ggData: GGData] = {
fullName: Rope.ROPE;
success: BOOL ← FALSE;
endOfStream: BOOL ← FALSE;
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.ROPE ← NARROW[event.rest.first];
startTimeCard: CARD ← IF 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:
BOOL ←
FALSE] = {
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.