ThymeViewers.mesa
Copyright (C) 1985 by Xerox Corporation. All rights reserved.
Last Edited by:
Sweetsun Chen, July 23, 1985 3:49:28 pm PDT
DIRECTORY
Buttons USING [Button, ButtonProc, Create, SetDisplayStyle],
Commander USING [CommandProc, Register],
Containers USING [ChildXBound, ChildYBound, Create],
FileNames USING [CurrentWorkingDirectory],
FS USING [Close, GetInfo, Open, OpenFile, Read],
Icons USING [IconFlavor, IconFileFormat],
Imager USING [Context, DoSave, MaskBox, SetColor, SetXY],
ImagerBackdoor USING [DrawBits, invert],
IO USING [Close, Flush, PutF, time],
Labels USING [Create, Label],
Menus USING [AppendMenuEntry, ChangeNumberOfLines, CreateMenu, CreateEntry,
GetLine, SetLine, MenuEntry
GetNumberOfLines, Menu, MenuLine, MenuProc],
MessageWindow USING [Append, Blink],
Rope USING [ROPE],
Rules USING [Create, Rule],
spGlobals USING[ConRec, DumpIt, FuncTable, Handle, Level2Model, ModelTable, NodeRec, NormalRun, RefConRec, RefNodeRec, RefR, RefUnReal, ResistorBody, ShowDetails, spFunctions, spModels, Stages, StopIt, ThymeToolRec, ToggleShowDetails, UnReal],
TerminalDefs USING [Cursor],
TiogaOps USING [FindDef, FindText, FindWord, SearchDir],
TypeScript USING [Create, TS],
VFonts USING[CharWidth, StringWidth],
ViewerBLT USING[ChangeNumberOfLines],
ViewerClasses USING [PaintProc, Viewer, ViewerClass, ViewerClassRec],
ViewerEvents USING [EventProc, RegisterEventProc],
ViewerIO USING [CreateViewerStreams],
ViewerOps USING [AddProp, CreateViewer, FetchProp, PaintViewer, RegisterViewerClass, RestoreViewer, SaveViewer, --SetMenu, --SetOpenHeight],
ViewerPrivate USING [selectedIcon],
ViewerTools USING [GetContents, MakeNewTextViewer, SetSelection],
VM USING [AddressForPageNumber, Allocate, Free, Interval];
ThymeViewers: CEDAR MONITOR
IMPORTS Buttons, Commander, Containers, FileNames, FS, Imager, ImagerBackdoor, IO, Labels, Menus, MessageWindow, Rules, spGlobals, TiogaOps, TypeScript, ViewerEvents, ViewerIO, ViewerOps, VFonts, ViewerBLT, ViewerPrivate, ViewerTools, VM
EXPORTS spGlobals = {
public global variables
version: PUBLIC Rope.ROPE← "Thyme - Cedar5.2 - May 1985";
refNodeRec: PUBLIC spGlobals.RefNodeRec= NEW[spGlobals.NodeRec];
refConRec: PUBLIC spGlobals.RefConRec= NEW[spGlobals.ConRec];
refR: PUBLIC spGlobals.RefR= NEW[spGlobals.ResistorBody];
refUnReal: PUBLIC spGlobals.RefUnReal= NEW[spGlobals.UnReal];
modelTable: PUBLIC spGlobals.ModelTable← NIL;
functionTable: PUBLIC spGlobals.FuncTable← NIL;
local constants
msgViewerH: CARDINAL = 100;
MakeThymeViewers: PUBLIC PROC[wDir: Rope.ROPENIL]
RETURNS [handle: spGlobals.Handle ← NIL] = {
thymeMenus: Menus.Menu;
handle ← NEW[spGlobals.ThymeToolRec ← []];
thymeMenus ← MakeThymeMenus[handle];
TRUSTED{
handle.outer ← Containers.Create[
info: [
name: version,
iconic: TRUE,
icon: private,
menu: thymeMenus,
column: left,
scrollable: FALSE],
paint: FALSE];
};
IF ViewerOps.FetchProp[handle.outer, $ThymeHandle] = NIL
THEN ViewerOps.AddProp[handle.outer, $ThymeHandle, handle];
IF ThymeNormalPaint = NIL THEN ThymeNormalPaint ← handle.outer.class.paint;
handle.outer.class.paint ← ThymeViewerPaint;
Menus.ChangeNumberOfLines[thymeMenus, 1];
MakeMainViewer[
handle: handle,
workingDir: IF wDir = NIL THEN FileNames.CurrentWorkingDirectory[] ELSE wDir];
MakeMsgViewer[handle];
ViewerOps.SetOpenHeight[handle.outer, handle.height + msgViewerH];
ViewerOps.PaintViewer[handle.outer, all];
}; -- MakeThymeViewers
menus
MakeThymeMenus: PROC[handle: spGlobals.Handle] RETURNS[menu: Menus.Menu] = {
requestConfirmMsg: Rope.ROPE = "Please confirm by clicking at it again.";
menu ← Menus.CreateMenu[];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Stop",
proc: Stop,
clientData: handle,
guarded: TRUE,
documentation: requestConfirmMsg]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Dump",
proc: Dump,
clientData: handle,
guarded: TRUE,
documentation: requestConfirmMsg]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[name: "Run", proc: Run, clientData: handle]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[name: "New", proc: New, clientData: handle]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[name: "TypeScriptMenus", proc: TypeScriptMenus,
clientData: handle]
];
typescript menus
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Save",
proc: Save,
clientData: handle,
guarded: TRUE,
documentation: requestConfirmMsg],
line: 1
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Reset",
proc: Reset,
clientData: handle,
guarded: TRUE,
documentation: requestConfirmMsg],
line: 1
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[name: "Find", proc: Find, clientData: handle],
line: 1
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[name: "Word", proc: Word, clientData: handle],
line: 1
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[name: "Def", proc: Def, clientData: handle],
line: 1
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[name: "FindError", proc: FindError, clientData: handle],
line: 1
];
}; -- MakeThymeMenus
Stop: ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
handle: spGlobals.Handle ← NARROW[clientData];
spGlobals.StopIt[handle];
}; -- Stop
Run: ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
handle: spGlobals.Handle ← NARROW[clientData];
spGlobals.NormalRun[handle];
}; -- Run
Dump: ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
handle: spGlobals.Handle ← NARROW[clientData];
spGlobals.DumpIt[handle];
}; -- Dump
New: Menus.MenuProc = {
handle: spGlobals.Handle ← NARROW[clientData];
[]← MakeThymeViewers[ViewerTools.GetContents[handle.wDir]];
};
TypeScriptMenus: ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
viewer: ViewerClasses.Viewer ← NARROW[parent];
menu: Menus.Menu ← viewer.menu;
numLines: Menus.MenuLine ← Menus.GetNumberOfLines[menu];
SELECT numLines FROM
1 => {
Menus.SetLine[menu, 1, tsMenuEntry];
numLines ← numLines + 1;
};
2 => numLines ← numLines - 1;
ENDCASE => ERROR;
ViewerBLT.ChangeNumberOfLines[viewer, numLines];
}; -- TypeScriptMenus
Save: ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
handle: spGlobals.Handle ← NARROW[clientData];
IF handle # NIL THEN {
IF handle.simulation # NIL THEN MessageWindow.Append["Thyme.log could NOT be saved when a circuit is being simulated.", TRUE]
ELSE IF handle.msgStream # NIL THEN {
handle.msgStream.PutF["\nSaved: %g\n", IO.time[]];
handle.msgStream.Flush[];
ViewerOps.SaveViewer[handle.message];
};
};
}; -- Save
Reset: ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
handle: spGlobals.Handle ← NARROW[clientData];
IF handle # NIL THEN {
IF handle.simulation # NIL THEN MessageWindow.Append["Thyme.log could NOT be reset when a circuit is being simulated.", TRUE]
ELSE IF handle.msgStream # NIL THEN {
ViewerOps.RestoreViewer[handle.message];
handle.msgStream.PutF["File: Thyme.log\nCreated: %g\n", IO.time[]];
};
};
}; -- Reset
Find: ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
handle: spGlobals.Handle ← NARROW[clientData];
IF handle # NIL THEN {
IF handle.msgStream # NIL AND handle.message # NIL THEN {
dir: TiogaOps.SearchDir = SELECT mouseButton FROM
red => forwards, yellow => anywhere, ENDCASE => backwards;
case: BOOL = NOT shift;
found: BOOL ← TiogaOps.FindText[
viewer: handle.message,
rope: NIL, -- automatically uses contents of primary selection
whichDir: dir,
which: primary,
case: case];
IF NOT found THEN BlinkMsg["Text not found."];
};
};
}; -- Find
Word: ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
handle: spGlobals.Handle ← NARROW[clientData];
IF handle # NIL THEN {
IF handle.msgStream # NIL AND handle.message # NIL THEN {
dir: TiogaOps.SearchDir = SELECT mouseButton FROM
red => forwards, yellow => anywhere, ENDCASE => backwards;
case: BOOL = NOT shift;
found: BOOL ← TiogaOps.FindWord[
viewer: handle.message,
rope: NIL, -- automatically uses contents of primary selection
whichDir: dir,
which: primary,
case: case];
IF NOT found THEN BlinkMsg["Text not found."];
};
};
}; -- Word
Def: ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
handle: spGlobals.Handle ← NARROW[clientData];
IF handle # NIL THEN {
IF handle.msgStream # NIL AND handle.message # NIL THEN {
dir: TiogaOps.SearchDir = SELECT mouseButton FROM
red => forwards, yellow => anywhere, ENDCASE => backwards;
case: BOOL = NOT shift;
found: BOOL ← TiogaOps.FindDef[
viewer: handle.message,
rope: NIL, -- automatically uses contents of primary selection
whichDir: dir,
which: primary,
case: case];
IF NOT found THEN BlinkMsg["Text not found."];
};
};
}; -- Def
FindError: ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
handle: spGlobals.Handle ← NARROW[clientData];
IF handle # NIL THEN {
IF handle.msgStream # NIL AND handle.message # NIL THEN {
dir: TiogaOps.SearchDir = SELECT mouseButton FROM
red => forwards, yellow => anywhere, ENDCASE => backwards;
case: BOOL = NOT shift;
found: BOOL ← TiogaOps.FindText[
viewer: handle.message,
rope: "g Error ",
whichDir: dir,
which: primary,
case: case];
IF NOT found THEN BlinkMsg["Text not found."];
};
};
}; -- FindError
BlinkMsg: PROC[msg: Rope.ROPE] = {
MessageWindow.Blink[];
MessageWindow.Append[msg, TRUE];
}; -- BlinkMsg
MakeMainViewer: PROC [handle: spGlobals.Handle, workingDir: Rope.ROPENIL] = {
entryHeight: CARDINAL = 12;
entryVSpace: CARDINAL = 6;
entryHSpace: CARDINAL = 10;
initialOutputName: Rope.ROPE = "<Output file name(s) will be set automatically.>";
outLabel, progressLabel, timeLabel, stepLabel: Labels.Label;
wDirButton, inputButton: Buttons.Button;
openInput: Buttons.Button;
initialInputName: Rope.ROPE = "<Specify input file name here.>";
handle.height ← handle.height + entryVSpace;
working directory
wDirButton ← Buttons.Create[
info: [
name: "Working Directory:",
parent: handle.outer,
wy: handle.height + 2,
wh: entryHeight, -- default the width so that it will be computed for us
border: FALSE ],
clientData: handle,
proc: DirPrompt,
paint: FALSE];
handle.wDir ← ViewerTools.MakeNewTextViewer[
info: [
parent: handle.outer,
wx: wDirButton.wx + wDirButton.ww + entryHSpace,
wy: handle.height,
ww: 80*VFonts.CharWidth['M],
wh: entryHeight + 2,
data: workingDir,
scrollable: FALSE,
border: FALSE],
paint: FALSE];
handle.height ← handle.height + entryHeight + entryVSpace;
input file
inputButton ← Buttons.Create[
info: [
name: "Input File:",
parent: handle.outer,
wy: handle.height + 2,
wh: entryHeight,
border: FALSE ],
clientData: handle,
proc: InputPrompt,
paint: FALSE];
handle.input ← ViewerTools.MakeNewTextViewer[
info: [
parent: handle.outer,
wx: inputButton.wx + inputButton.ww + entryHSpace,
wy: handle.height,
ww: 80*VFonts.CharWidth['M],
wh: entryHeight + 2,
data: initialInputName,
scrollable: FALSE,
border: FALSE],
paint: FALSE];
handle.height ← handle.height + entryHeight + entryVSpace;
output file
outLabel ← Labels.Create[
info: [
name: "Output File:",
parent: handle.outer,
wy: handle.height,
border: FALSE ],
paint: FALSE];
handle.output ← Labels.Create[
info: [
name: initialOutputName,
parent: handle.outer,
wx: VFonts.StringWidth["Output File:"] + entryHSpace,
wy: handle.height + 2,
ww: 80*VFonts.CharWidth['W],
wh: entryHeight,
border: FALSE],
paint: FALSE];
handle.height ← handle.height + entryHeight + entryVSpace;
progress cursor
progressLabel ← Labels.Create[
info: [name: "Progress:", parent: handle.outer,
wy: handle.height, border: FALSE ],
paint: FALSE];
handle.progress ← ViewerOps.CreateViewer[
flavor: $ThymeCursor,
info: [
parent: handle.outer,
wx: progressLabel.wx + progressLabel.ww + entryHSpace,
wy: handle.height,
ww: 16,
wh: 16,
data: idleBitmap,
border: FALSE],
paint: FALSE];
handle.showDetailsButton ← Buttons.Create[
info: [
name: "details:",
parent: handle.outer,
wx: handle.progress.wx + handle.progress.ww + entryHSpace + entryHSpace,
wy: handle.height,
wh: entryHeight + 3,
border: TRUE ],
clientData: handle,
proc: ShowDetailsProc,
paint: FALSE];
Buttons.SetDisplayStyle[handle.showDetailsButton,
IF spGlobals.ShowDetails[handle] THEN $WhiteOnBlack ELSE $BlackOnWhite];
timeLabel ← Labels.Create[
info: [
name: "time:",
parent: handle.outer,
wx: handle.showDetailsButton.wx + handle.showDetailsButton.ww + entryHSpace,
wy: handle.height + 2,
wh: entryHeight,
border: FALSE ],
paint: FALSE];
handle.time ← Labels.Create[
info: [
parent: handle.outer,
wx: timeLabel.wx + timeLabel.ww + entryHSpace,
wy: handle.height + 2,
ww: 10*VFonts.StringWidth["M"],
wh: entryHeight,
border: FALSE],
paint: FALSE];
stepLabel ← Labels.Create[
info: [
name: "step:",
parent: handle.outer,
wx: handle.time.wx + handle.time.ww + entryHSpace,
wy: handle.height + 2,
wh: entryHeight,
border: FALSE ],
paint: FALSE];
handle.step ← Labels.Create[
info: [
parent: handle.outer,
wx: stepLabel.wx + stepLabel.ww + entryHSpace,
wy: handle.height + 2,
ww: 15*VFonts.StringWidth["M"],
wh: entryHeight,
border: FALSE],
paint: FALSE];
handle.height ← handle.height + entryHeight + entryVSpace;
}; -- MakeMainViewer
DirPrompt: Buttons.ButtonProc = {
handle: spGlobals.Handle ← NARROW[clientData];
IF handle # NIL THEN ViewerTools.SetSelection[handle.wDir];
}; -- InputPrompt
InputPrompt: Buttons.ButtonProc = {
handle: spGlobals.Handle ← NARROW[clientData];
IF handle # NIL THEN ViewerTools.SetSelection[handle.input];
}; -- InputPrompt
ShowDetailsProc: Buttons.ButtonProc = {
force the selection into the user input field
handle: spGlobals.Handle ← NARROW[clientData];
IF handle # NIL THEN {
spGlobals.ToggleShowDetails[handle];
Buttons.SetDisplayStyle[handle.showDetailsButton,
IF spGlobals.ShowDetails[handle] THEN $WhiteOnBlack ELSE $BlackOnWhite];
};
}; -- InputPrompt
message viewer
MakeMsgViewer: PROC [handle: spGlobals.Handle] = {
divider
rule: Rules.Rule ← Rules.Create[
info: [
parent: handle.outer,
wy: handle.height,
ww: handle.outer.cw,
wh: 1],
paint: FALSE];
Containers.ChildXBound[handle.outer, rule];
handle.height← handle.height + 2;
the typescript
handle.message← TypeScript.Create[
info: [
parent: handle.outer,
wy: handle.height,
ww: handle.outer.cw,
wh: 800,
border: FALSE,
file: "Thyme.log"],
paint: FALSE
];
IF ViewerOps.FetchProp[handle.message, $ThymeHandle] = NIL
THEN ViewerOps.AddProp[handle.message, $ThymeHandle, handle];
[in: , out: handle.msgStream] ← ViewerIO.CreateViewerStreams[
name: "Thyme.log", viewer: handle.message, backingFile: "Thyme.log"];
IO.PutF[handle.msgStream, "File: Thyme.log\nCreated: %g\n", IO.time[]];
Containers.ChildXBound[handle.outer, handle.message];
Containers.ChildYBound[handle.outer, handle.message];
}; -- MakeMsgViewer
destroy proc
MyDestroy: ViewerEvents.EventProc = {
h: spGlobals.Handle ← NARROW[ViewerOps.FetchProp[viewer, $ThymeToolData]];
IF h=NIL THEN RETURN;
IF event=destroy AND before THEN {
h.msgStream.Close[];
};
}; -- MyDestroy
painting
iconW: INTEGER = 64;
iconH: INTEGER = 64;
StatusBitmap: TYPE = REF StatusPattern;
StatusPattern: TYPE = TerminalDefs.Cursor; -- ARRAY [0..16) OF WORD
thymeIconBitmap: REF IconPattern;
IconPattern: TYPE = ARRAY [0..iconH*iconW/16) OF WORD;
idleBitmap, inputBitmap, bombBitmap, topoBitmap: StatusBitmap;
runBitmap: ARRAY [0..4) OF StatusBitmap;
ThymeViewerPaint: ViewerClasses.PaintProc = {
IF self.iconic THEN [] ← ThymeIconPaint[self, context, whatChanged, clear]
ELSE [] ← ThymeNormalPaint[self, context, whatChanged, clear];
}; -- ThymeViewerPaint
ThymeNormalPaint: ViewerClasses.PaintProc ← NIL; -- will be assigned Containers PaintProc
ThymeIconPaint: ViewerClasses.PaintProc = {
handle: spGlobals.Handle;
bitmap: StatusBitmap;
drawFace: PROC = {
context.SetXY[[0, iconH]];
ImagerBackdoor.DrawBits[context: context, base: LOOPHOLE[thymeIconBitmap],
wordsPerLine: iconW/16, sMin: 0, fMin: 0, sSize: iconH, fSize: iconW, tx: 0, ty: iconH];
};
drawStatus: PROC = {
context.SetXY[[25, 39]];
ImagerBackdoor.DrawBits[context: context, base: LOOPHOLE[bitmap],
wordsPerLine: 1, sMin: 0, fMin: 0, sSize: 15, fSize: 15, tx: 25, ty: 39];
};
invertProc: PROC = {
context.SetColor[ImagerBackdoor.invert];
context.MaskBox[[0, 0, iconW, iconH]];
};
handle ← NARROW[ViewerOps.FetchProp[self, $ThymeHandle]];
bitmap ← NARROW[handle.progress.data];
IF whatChanged # $ThymeStatus THEN context.DoSave[drawFace];
context.DoSave[drawStatus];
IF ViewerPrivate.selectedIcon = self THEN context.DoSave[invertProc];
}; -- ThymeIconPaint
MakeBitmaps: PROC[file: Rope.ROPE ← "Thyme.icons"]= TRUSTED {
iconRaster: INTEGER = 4;
fh: FS.OpenFile ← FS.Open[file];
pages: INTFS.GetInfo[fh].pages;
space: VM.Interval ← VM.Allocate[count: pages];
iconBase: LONG POINTER TO Icons.IconFileFormat
VM.AddressForPageNumber[space.page];
cursorsBase: LONG POINTER TO Icons.IconFileFormat
iconBase + SIZE[Icons.IconFileFormat];
MakeCursorBitmap: PROC[offset: INTEGER] RETURNS [bitmap: StatusBitmap] = TRUSTED{
bitmap ← NEW[StatusPattern];
FOR i: INTEGER IN [0..16) DO
bitmap[i] ← cursorsBase.bits[offset + i * iconRaster];
ENDLOOP;
}; -- MakeBitmap
FS.Read[file: fh, from: 0, nPages: pages, to: iconBase];
thymeIconBitmap ← NEW[IconPattern ← iconBase.bits];
idleBitmap ← MakeCursorBitmap[0];
inputBitmap ← MakeCursorBitmap[1];
bombBitmap ← MakeCursorBitmap[2];
topoBitmap ← MakeCursorBitmap[3];
FOR ir: CARDINAL IN [0..4) DO
runBitmap[ir] ← MakeCursorBitmap[16 * iconRaster + ir];
ENDLOOP;
VM.Free[space];
FS.Close[fh];
}; -- MakeBitmaps
SetCursor: PUBLIC PROC [handle: spGlobals.Handle] = {
ENABLE UNWIND => NULL;
d: REF ANY;
IF handle = NIL THEN RETURN;
IF handle.progress = NIL THEN RETURN;
SELECT handle.stage FROM
idle => d ← idleBitmap;
input => d ← inputBitmap;
bomb => d ← bombBitmap;
topo => d ← topoBitmap;
run => d ←
IF handle.vars = NIL THEN runBitmap[0]
ELSE runBitmap[handle.vars.runState];
ENDCASE =>
BlinkMsg["* Weird situation at SetCursor detected. Please inform Thyme implementor."];
handle.progress.data ← d;
IF handle.outer.iconic THEN
ViewerOps.PaintViewer[handle.outer, client, FALSE, $ThymeStatus]
ELSE ViewerOps.PaintViewer[handle.progress, client, FALSE, $ThymeStatus];
}; -- SetCursor
ThymeCursorPaint: ViewerClasses.PaintProc = {
ctx: Imager.Context ← context;
bitmap: StatusBitmap ← NARROW[self.data];
proc: PROC = {
ctx.SetXY[[0, 16]];
ImagerBackdoor.DrawBits[context: ctx, base: LOOPHOLE[bitmap],
wordsPerLine: 1, sMin: 0, fMin: 0, sSize: 15, fSize: 15, tx: 0, ty: 16];
};
ctx.DoSave[proc];
}; -- ThymeCursorPaint
thymeCursorClass: ViewerClasses.ViewerClass ←
NEW[ViewerClasses.ViewerClassRec ← [
paint: ThymeCursorPaint,
tipTable: NIL]
];
MakeThyme: Commander.CommandProc = {[] ← MakeThymeViewers[];};
MakeBitmaps[];
ViewerOps.RegisterViewerClass[$ThymeCursor, thymeCursorClass];
Commander.Register[
key: "Thyme",
proc: MakeThyme,
doc: "Start an instance of the circuit simulator Thyme." ];
[] ← ViewerEvents.RegisterEventProc[proc: MyDestroy, event: destroy];
build the library functions and models
START spGlobals.spModels;
START spGlobals.Level2Model;
START spGlobals.spFunctions;
}.
CHANGE LOG
Chen, June 12, 1984 11:21:28 am PDT, created.
Chen, May 14, 1985 8:05:48 pm PDT, implemented custom icon that shows status.
Chen, July 19, 1985 2:59:06 pm PDT, => Cedar6.0. (Cedar Graphics -> Imager, etc.)