SilFileImpl.mesa
Last Edited by: Larrabee, March 29, 1984 1:03:38 pm PST
Last Edited by: Pier, December 18, 1985 5:07:50 pm PST
This module is the custodian of the internal data structures built for Sil files. Any creations, deletions, or queries will pass through here.
DIRECTORY
Basics USING [bytesPerWord],
FileNames USING [CurrentWorkingDirectory],
FS USING [Error, StreamOpen],
IO USING [Close, EndOf, EndOfStream, GetChar, PutChar, PutRope, STREAM, UnsafeGetBlock, UnsafePutBlock, GetIndex, SetIndex],
MessageWindow USING [Append, Blink],
Rope USING [Concat, Fetch, FromChar, FromProc, Length, ROPE],
RuntimeError USING [BoundsFault],
SilDisplayCursors,
SilFile,
SilKernel,
SilUserInput,
UserProfile USING [ListOfTokens, Token]
;
SilFileImpl: CEDAR MONITOR IMPORTS Rope, IO, FS, FileNames, MessageWindow, RuntimeError, UserProfile, SilKernel, SilDisplayCursors EXPORTS SilFile, SilKernel ~ BEGIN OPEN SilFile;
GLOBAL VARIABLES:
Data common to all Sil files which are being viewed or edited at any one time:
While there may be many sil files open and in use at any given time, they should all be using the same library and fonts (they are set in the user profile).
allModelsMacros: ARRAY LibraryFonts OF CharSet;
allModelsFontNames: ARRAY PresetFonts OF ROPE; -- names of display fonts
allModelsPFontNames: ARRAY PresetFonts OF ROPE;-- names of print fonts
THE Selection. There can only be one Sil selection at any given time.
allSelection: SilSelection;
Individual data for each Sil file being edited:
This is the concrete representation of an opaque type defined in SilKernel.
SilModel: TYPE ~ REF SilModelRec;
SilModelRec: PUBLIC TYPE ~ MONITORED RECORD [
fgnd: SilObject ← NIL, --All the foreground objects in the main picture
bkgnd: SilObject ← NIL, --All the top-level background objects in the main picture
deleted: DeleteArray,
macros: CharSet, --All the macros in this file
activeName: ROPE, --The name of currently active file
modelIsBuilt: BOOL --True if file has run through BUILD prog
];
CharSet: TYPE ~ ARRAY MacroName OF MacroDef; 
MacroDef: TYPE ~ RECORD [
xMin, yMin: INTEGER, -- Upper Left boundary of macro definition
xMax, yMax: INTEGER, -- Lower right boundary of macro definition
def: SilObject ← NIL
];
DeleteArray: TYPE ~ ARRAY DeletesToKeep OF SilObject ← deleteInit;
DeletesToKeep: TYPE ~ [1..5];
deleteInit: DeleteArray ← [NIL, NIL, NIL, NIL , NIL];
deleteTop: DeletesToKeep ~ 1;
deleteBottom: DeletesToKeep ~ 5;
ROPE: TYPE ~ Rope.ROPE;
STREAM: TYPE ~ IO.STREAM;
NewSilModel: PUBLIC PROC [] RETURNS [model: SilModel] ~ {
Create an empty Sil Model.
model ← NEW[SilModelRec];
model.activeName ← FileNames.CurrentWorkingDirectory[];
};
InitSil: PUBLIC PROC [] ~ {
This procedure will initialize all of Sil's internal structure by reading in any library files and retreiving the names of any fonts.
! SilError with explain =
BadFile: One of the library files contained some sort sort of lie concerning its length (such as a non-integral number of Sil objects).
BadFileName: a Sil file name was given in the user profile which referenced a file which could not be opened.
dummySobr: SilObjectRec;
libsOK: BOOLEANTRUE;
key, ropeResult: ROPE;
ListResult: TYPE ~ LIST OF ROPE;
listResult: ListResult;
PresetDefaults: TYPE ~ ARRAY PresetFonts OF ListResult;
presetDefaults: PresetDefaults ~ [
LIST["Helvetica10B", "Helvetica10"],
LIST["Helvetica7", "Helvetica7"],
LIST["Template64", "Template64"],
LIST["Gates32", "Gates32"]
];
LibraryDefaults: TYPE ~ ARRAY LibraryFonts OF ROPE;
libraryDefaults: LibraryDefaults ~ ["sil.lb5", "sil.lb6", "sil.lb7", "sil.lb8", "sil.lb9"];
profilePrefix: ROPE ~ "Sil.Font";
First get the predefined fonts . . .
FOR i: PresetFonts IN PresetFonts DO
key ← Rope.Concat[profilePrefix, Rope.FromChar['0 + i] ];
listResult ← UserProfile.ListOfTokens[key: key, default: presetDefaults[i] ];
If there is a one element list, that element is used for both display and print font
If there is a more than one element list (should be only 2), the first element is the display font and the second element is the print font
allModelsFontNames[i] ← listResult.first;
listResult ← listResult.rest;
allModelsPFontNames[i] ← IF listResult=NIL THEN allModelsFontNames[i] ELSE listResult.first;
ENDLOOP;
Now get the Library fonts (if there are any) working . . .
MessageWindow.Append["Sil Library Files ", TRUE];
FOR i: LibraryFonts IN LibraryFonts DO
key ← Rope.Concat[profilePrefix, Rope.FromChar['0 + i] ];
ropeResult ← UserProfile.Token[key: key, default: libraryDefaults[i] ];
Now read in the the Library files specified.
FileToInternalRep[model: NIL, name: ropeResult, macroFile: TRUE, picFont: i
! SilKernel.SilError => {
MessageWindow.Append[ropeResult, FALSE];
MessageWindow.Append[" ", FALSE];
libsOK ← FALSE;
CONTINUE}];
ENDLOOP;
IF libsOK THEN MessageWindow.Append["loading A-OK", FALSE] ELSE MessageWindow.Append["loading failed", FALSE];
put a dummy object on the END of the Select List
dummySobr ← [xMin: 0, yMin: 0, xMax: 0, yMax: 0, color: 0, font: 0, italic: FALSE, value: NIL, selectObj: NIL];
allSelection.lastObject ← LIST[dummySobr];
allSelection.objects ← allSelection.lastObject;
};
MainFileToModel: PUBLIC PROC [model: SilModel, name: ROPE, relative: BOOL] ~ {
This should be called after Sil has been initialized, and new model has been created.
This proc will add any data in the named file to the main picture data currently in the model provided. If relative, file objects will be origined at (MarkX, MarkY) instead of (0, 0).
! SilError with explain =
BadFile: This Sil file contained some sort sort of lie concerning its length (such as a non-integral number of Sil objects).
BadFileName: No file could be opened for Read-only given this name.
Do not catch SilError; will be caught by caller.
FileToInternalRep[model: model, name: name, relative: relative]; -- raises SilKernel.SilError if filename is bad
model.activeName ← name;
};
ModelToFile: PUBLIC PROC [model: SilModel, name: ROPE, clipIt: BOOLFALSE, large: BOOLFALSE, xMin, yMin, xMax, yMax: INTEGER ← 0] RETURNS [wasClipped: BOOLFALSE] ~ {
This will output all the information in this model to the file name given. If clipIt then only output those objects which lie entirely inside the region given.
raises SilKernel.SilError with explain = BadFileName: No file could be opened with this name. Do not catch SilError; will be caught by caller. Don't assign a new model.activeName if writing fails.
wasClipped ← InternalRepToFile[model, name, clipIt, large, xMin, yMin, xMax, yMax];
model.activeName ← name;
};
ClearModel: PUBLIC ENTRY PROC [model: SilModel] ~ {
This will delete the model's main picture as well as any macro definitions.
IF allSelection.model = model THEN {
allSelection ← [model: NIL, objects: allSelection.lastObject, numObjects: 0, xMin: 0, yMin: 0, xMax: 0, yMax: 0];
NEVER change allSelection.lastObject
};
model.fgnd ← model.bkgnd ← NIL;
model.deleted ← deleteInit;
ClearMacros[model];
};
ClearMacros: PUBLIC PROC [model: SilModel] ~ {
This will cause macro definitions in the model to be discarded, along with any macro objects.
Note that macros that contain bkgnd boxes are themselves always in the fgnd
FOR c: MacroName IN MacroName DO
model.macros[c].def ← NIL;
ENDLOOP;
FOR s: SilObject ← model.fgnd, s.rest WHILE s # NIL DO
IF s.first.font IN InternalFileMacroFonts THEN DeleteObject[model, s];
ENDLOOP;
};
CheckSelectionsForMacroDefs: PUBLIC ENTRY PROC [model: SilModel, disallowName: BOOLFALSE, name: MacroName ← '!] RETURNS [SelectionOk: BOOLTRUE] ~ {
Check the selection to see if it references macro's that are not defined in model. If disallowName, make sure that the selection does not reference any macro's with name name.
FOR s: SilObject ← allSelection.objects, s.first.selectObj WHILE s # allSelection.lastObject DO
sobr: SilObjectRec ← s.first;
SELECT sobr.font FROM
IN InternalBoxFonts, IN InternalLibraryFonts => LOOP;
IN InternalFileMacroFonts => {
len: INT ← Rope.Length[sobr.value];
FOR i: INT IN [0..len) DO
c: CHAR ← Rope.Fetch[sobr.value, i];
IF c =' THEN LOOP; --ignore spaces
IF model.macros[c].def = NIL THEN RETURN[FALSE];
IF disallowName AND c = name THEN RETURN[FALSE];
ENDLOOP;
};
ENDCASE;
ENDLOOP;
RETURN[TRUE];
};
DefineMacroFromSelection: PUBLIC PROC [model: SilModel, name: MacroName ← '!, xRef, yRef: INTEGER] ~ {
Add the macro definition with name name to the model given.
sSel: SilSelection ← CopySelection[]; --CopySelection deletes the final dummy object
FOR s: SilObject ← sSel.objects, s.rest WHILE s # sSel.lastObject DO
s.first.xMin ← s.first.xMin - xRef;
s.first.yMin ← s.first.yMin - yRef;
s.first.xMax ← s.first.xMax - xRef;
s.first.yMax ← s.first.yMax - yRef;
ENDLOOP;
model.macros[name].def ← sSel.objects;
model.macros[name].xMin ← 0;
model.macros[name].yMin ← 0;
model.macros[name].xMax ← sSel.xMax - xRef;
model.macros[name].yMax ← sSel.yMax - yRef;
};
GetObjectList: PUBLIC PROC [model: SilModel ← NIL, mode: PictureType ← none,
 fontNumber: InternalFonts ← 8, c: CHAR ← '!] RETURNS [oblist: SilObject] ~ {
Return the pertinent object list (main, or macro) out of the model. If mode is main, ignore fontNumber and c. The InnerProc is necessary in order to catch RuntimeError.BoundsFault.
InnerProc: PROC RETURNS [SilObject] = {
SELECT mode FROM
fgnd => RETURN[model.fgnd];
bkgnd => RETURN[model.bkgnd];
macro => SELECT fontNumber FROM
IN InternalFileMacroFonts => RETURN[model.macros[c].def ];
IN InternalLibraryFonts => RETURN[allModelsMacros[fontNumber - internalMacroOffset][c].def ];
ENDCASE => RETURN[NIL];
ENDCASE => RETURN[NIL];
};
internalMacroOffset: FileMacroFonts ~ 4;
IF mode=none THEN ERROR; -- temporary debugging statement
oblist ← InnerProc [ ! RuntimeError.BoundsFault => GOTO NoSuchChar]; --raised if c is out of range
EXITS
NoSuchChar => oblist ← NIL;
};
GetMacroBoundingBox: PUBLIC PROC [model: SilModel, fontNumber: InternalFonts ← 8, c: CHAR ← '!] RETURNS [xMin, yMin, xMax, yMax: INTEGER] ~ {
Return the bounding box of the macro given.The InnerProc is needed to catch RuntimeError.BoundsFault.
InnerProc: PROC RETURNS [xMin, yMin, xMax, yMax: INTEGER] = {
SELECT fontNumber FROM
IN InternalFileMacroFonts => RETURN[model.macros[c].xMin, model.macros[c].yMin, model.macros[c].xMax, model.macros[c].yMax];
IN InternalLibraryFonts => RETURN[allModelsMacros[fontNumber-internalMacroOffset][c].xMin, allModelsMacros[fontNumber-internalMacroOffset][c].yMin, allModelsMacros[fontNumber-internalMacroOffset][c].xMax, allModelsMacros[fontNumber-internalMacroOffset][c].yMax];
ENDCASE => RETURN[0,0,0,0];
};
internalMacroOffset: FileMacroFonts ~ 4;
[xMin: xMin, yMin: yMin, xMax: xMax, yMax: yMax] ← InnerProc [ ! RuntimeError.BoundsFault => GOTO NoSuchChar]; --raised if c is out of range
EXITS
NoSuchChar => RETURN[0,0,0,0];
};
GetSelection: PUBLIC PROC [] RETURNS [selection: SilSelection] ~ {
RETURN[allSelection];
};
EvaluateSelection: PUBLIC ENTRY PROC ~ {
IF allSelection.numObjects <= 0 THEN RETURN;
allSelection.xMin ← allSelection.yMin ← LAST[INTEGER];
allSelection.xMax ← allSelection.yMax ← FIRST[INTEGER];
FOR sob: SilObject ← allSelection.objects, sob.first.selectObj WHILE sob # allSelection.lastObject DO
allSelection.xMin ← MIN[allSelection.xMin, sob.first.xMin];
allSelection.yMin ← MIN[allSelection.yMin, sob.first.yMin];
allSelection.xMax ← MAX[allSelection.xMax, sob.first.xMax];
allSelection.yMax ← MAX[allSelection.yMax, sob.first.yMax];
ENDLOOP;
};
CopySelection: PUBLIC ENTRY PROC [] RETURNS [copiedSelection: SilSelection] ~ {
Return a copy of the selection which has all the objects of the real selection, but no mention of their model and not linked by their selectObj fields, and no lastObject record.
copiedSelection.model ← NIL;
copiedSelection.objects ← NIL;
copiedSelection.numObjects ← allSelection.numObjects;
copiedSelection.xMin ← allSelection.xMin;
copiedSelection.yMin ← allSelection.yMin;
copiedSelection.xMax ← allSelection.xMax;
copiedSelection.yMax ← allSelection.yMax;
FOR s: SilObject ← allSelection.objects, s.first.selectObj WHILE s # allSelection.lastObject DO
sobr: SilObjectRec ← s.first;
sobr.selectObj ← NIL;
copiedSelection.objects ← JoinObjectLists[copiedSelection.objects, LIST[sobr] ];
ENDLOOP;
};
ObjectAtPos: PUBLIC PROC [model: SilModel, x, y: INTEGER] RETURNS [sob: SilObject ← NIL] ~ {
In model, find the object at <x, y> with the smallest bounding box and return that object. If there is no object at <x, y> return NIL.
InnerProc: PROC [obj: SilObject] = {
FOR s: SilObject ← obj, s.rest WHILE s # NIL DO
IF PointInBBox[x, y, s.first] THEN {
newPerim: INTEGER ← BoxPerimeter[s.first];
IF newPerim < currentPerim THEN {
sob ← s;
currentPerim ← newPerim;
};
};
ENDLOOP;
};
currentPerim: INTEGERLAST[INTEGER];
InnerProc [model.fgnd];
InnerProc [model.bkgnd];
};
DefineSelectWithBox: PUBLIC PROC [model: SilModel, xMin, yMin, xMax, yMax: INTEGER] RETURNS [objXMin: INTEGERLAST[INTEGER], objYMin: INTEGERLAST[INTEGER], objXMax: INTEGERFIRST[INTEGER], objYMax: INTEGERFIRST[INTEGER]] ~ {
In model, find all the objects inside the box defined by xMin, yMin, xMax, and yMax and add them to the selection. If there are no objects in the box, add no elements to the selection.
Return the actual minimum and maximum dimensions of objects inside the box (if there are no objects within the box return the minimum and maximums provided).
InnerProc: PROC [obj: SilObject] = {
FOR s: SilObject ← obj, s.rest WHILE s # NIL DO
IF (s.first.xMin < xMin OR s.first.yMin < yMin OR s.first.xMax > xMax OR s.first.yMax > yMax) THEN LOOP;
DefineSelectWithObject[model, s];
objXMin ← MIN[objXMin, s.first.xMin];
objYMin ← MIN[objYMin, s.first.yMin];
objXMax ← MAX[objXMax, s.first.xMax];
objYMax ← MAX[objYMax, s.first.yMax];
someBox ← TRUE;
ENDLOOP;
};
temp: INTEGER;
someBox: BOOLFALSE;
IF xMin > xMax THEN {
temp ← xMin;
xMin ← xMax;
xMax ← temp;
};
IF yMin > yMax THEN {
temp ← yMin;
yMin ← yMax;
yMax ← temp;
};
InnerProc [model.fgnd];
InnerProc [model.bkgnd];
IF someBox THEN RETURN ELSE RETURN[xMin, yMin, xMax, yMax];
};
DefineSelectWithObject: PUBLIC ENTRY PROC [model: SilModel, sob: SilObject] ~ {
Sob is added to the previously selected objects. If Sob is from a new model flush the old selection and start a new one.
IF sob=NIL THEN RETURN;
SELECT TRUE FROM
model = allSelection.model AND NOT ObjectIsSelected[model, sob] => {
sob.first.selectObj ← allSelection.objects;
allSelection.objects ← sob;
allSelection.numObjects ← allSelection.numObjects + 1;
IF sob.first.selectObj = allSelection.lastObject THEN {
allSelection.xMin ← sob.first.xMin;
allSelection.yMin ← sob.first.yMin;
allSelection.xMax ← sob.first.xMax;
allSelection.yMax ← sob.first.yMax;
}
ELSE {
allSelection.xMin ← MIN[allSelection.xMin, sob.first.xMin];
allSelection.yMin ← MIN[allSelection.yMin, sob.first.yMin];
allSelection.xMax ← MAX[allSelection.xMax, sob.first.xMax];
allSelection.yMax ← MAX[allSelection.yMax, sob.first.yMax];
};
};
model # allSelection.model => {
LocalDeselectAll[];
sob.first.selectObj ← allSelection.lastObject;
allSelection.model ← model;
allSelection.objects ← sob;
allSelection.numObjects ← 1;
allSelection.xMin ← sob.first.xMin;
allSelection.yMin ← sob.first.yMin;
allSelection.xMax ← sob.first.xMax;
allSelection.yMax ← sob.first.yMax;
};
ENDCASE;
};
Deselect: PUBLIC ENTRY PROC [model: SilModel, sob: SilObject] ~ {
LocalDeselect[model: model, sob: sob];
};
LocalDeselect: PROC [model: SilModel, sob: SilObject] ~ {
Deselect sob.
IF sob=NIL OR NOT ObjectIsSelected[model, sob] THEN RETURN;
allSelection.numObjects ← allSelection.numObjects - 1;
IF sob = allSelection.objects THEN allSelection.objects ← sob.first.selectObj
ELSE {
s, olds: SilObject;
FOR s ← allSelection.objects, s.first.selectObj WHILE (s#sob AND s#allSelection.lastObject) DO
olds ← s;
ENDLOOP;
IF s=allSelection.lastObject THEN ERROR; -- should have been guaranteed that sob is on the list, so this "can't happen"
IF s=sob THEN olds.first.selectObj ← s.first.selectObj;
};
sob.first.selectObj ← NIL;
[allSelection.xMin, allSelection.yMin, allSelection.xMax, allSelection.yMax] ← BoundingBoxOfSelection[];
};
DeselectAll: PUBLIC ENTRY PROC [] ~ {
LocalDeselectAll[];
};
LocalDeselectAll: PROC [] ~ {
Deselect all objects.
olds: SilObject;
s: SilObject ← allSelection.objects; -- can't define s in following loop because loop must modify s
WHILE s # allSelection.lastObject DO
olds ← s; -- remember the s LIST
s ← s.first.selectObj; -- make s a new LIST
olds.first.selectObj ← NIL; -- NIL out the original s.first.selectLink
ENDLOOP;
allSelection.objects ← allSelection.lastObject;
allSelection.model ← NIL;
allSelection.numObjects ← 0;
};
DeleteAndCacheSelection: PUBLIC ENTRY PROC [cache: BOOLFALSE] ~ {
Delete all objects which are selected. IF cache, put deleted objects on delete queue.
IF allSelection.model#NIL THEN {
model: SilModel ← allSelection.model;
nextObj: SilObject;
IF cache THEN PushDeleteQueue[model];
FOR s: SilObject ← allSelection.objects, nextObj WHILE s # allSelection.lastObject DO
nextObj ← s.first.selectObj; -- remember old selectObj
DeleteObject[model, s];
s.rest ← NIL; s.first.selectObj ← NIL;
IF cache THEN model.deleted[deleteTop] ← JoinObjectLists[model.deleted[deleteTop], s];
ENDLOOP;
allSelection.objects ← allSelection.lastObject;
allSelection.model ← NIL;
allSelection.numObjects ← 0;
};
};
DeleteAndCacheObject: PUBLIC PROC [model: SilModel, sob: SilObject] ~ {
Delete this Specific object (delink it from the selection list if necessary).
IF sob # NIL THEN {
PushDeleteQueue[model];
model.deleted[deleteTop] ← sob;
DeleteObject[model, sob];
model.deleted[deleteTop].rest ← NIL;
model.deleted[deleteTop].first.selectObj ← NIL;
};
};
DeleteObject: PROC [model: SilModel, sob: SilObject] ~ {
Delete this Specific object (delink it from the selection list if necessary).
IF sob#NIL THEN {
oldObj: SilObject;
LocalDeselect[model, sob];
SELECT TRUE FROM
model.fgnd=sob => model.fgnd ← model.fgnd.rest;
model.bkgnd=sob => model.bkgnd ← model.bkgnd.rest;
ENDCASE => {
oldObj ← model.fgnd;
FOR s: SilObject ← model.fgnd, s.rest WHILE s # NIL DO
IF s = sob THEN {oldObj.rest ← s.rest; RETURN;}
ELSE oldObj ← s;
ENDLOOP;
oldObj ← model.bkgnd;
FOR s: SilObject ← model.bkgnd, s.rest WHILE s # NIL DO
IF s = sob THEN {oldObj.rest ← s.rest; RETURN;}
ELSE oldObj ← s;
ENDLOOP;
};
};
};
Undelete: PUBLIC PROC [model: SilModel] RETURNS [sob: SilObject] ~ {
Undelete the most recently deleted objects for this model and make it the selection.
nextSob: SilObject;
DeselectAll[];
sob ← PopDeleteQueue[model];
FOR s: SilObject ← sob, nextSob WHILE s # NIL DO
nextSob ← s.rest;
IF s.first.font IN InternalBackgroundBoxFonts THEN {s.rest ← model.bkgnd; model.bkgnd ← s}
ELSE {s.rest ← model.fgnd; model.fgnd ← s};
DefineSelectWithObject[model, s];
ENDLOOP;
};
AddObjectToMainPicture: PUBLIC PROC [model: SilModel, sobr: SilObjectRec] RETURNS [sob: SilObject ← NIL] ~ {
Add new object to the main picture of model.
sob ← LIST[sobr];
IF sobr.font IN InternalBackgroundBoxFonts THEN model.bkgnd ← JoinObjectLists[model.bkgnd, sob]
ELSE model.fgnd ← JoinObjectLists[model.fgnd, sob];
};
internalMacroOffset: FileMacroFonts ~ 4;
InternalFontFromUserFont: PUBLIC PROC [font: UserFonts, bold: BOOL] RETURNS [ifont: InternalFonts] ~ {
Return the internal font corresponding to the user font and boldness given.
SELECT font FROM
IN PresetFonts => RETURN[IF bold THEN font * 2 + 1 ELSE font * 2];
IN MacroFonts => RETURN[font + internalMacroOffset];
ENDCASE => ERROR;
};
UserFontFromInternalFont: PUBLIC PROC [ifont: InternalFonts] RETURNS [font: UserFonts, bold: BOOL] ~ {
Return the user font and boldness corresponding to the internal font given.
SELECT ifont FROM
IN InternalPresetFonts => RETURN[ifont / 2, FontIsBold[ifont]];
IN InternalMacroFonts => RETURN[ifont - internalMacroOffset, FALSE];
ENDCASE => ERROR;
};
FontIsBold: PUBLIC PROC [font: InternalFonts] RETURNS [bold: BOOL] ~ {
True if this internal font corresponds to a bold user font.
SELECT font FROM
1,3,5,7 => RETURN [TRUE];
0,2,4,6,8,9,10,11,12,13,14,15 => RETURN[FALSE];
ENDCASE => RETURN[FALSE];
};
FontNameFromInternalFont: PUBLIC PROC [font: PresetFonts, fontType: FontType ← display] RETURNS [fName: ROPE] ~ {
Return the correct Font name for the given Sil font.
RETURN [IF fontType = display THEN allModelsFontNames[font] ELSE allModelsPFontNames[font]];
};
GetActiveFileName: PUBLIC PROC [model: SilModel] RETURNS [name: ROPE] ~ {
Get the name of the currently active Sil file for this model.
IF (model.fgnd#NIL OR model.bkgnd#NIL) THEN RETURN [model.activeName] ELSE RETURN[""];
};
GetLastActiveFileName: PUBLIC PROC [model: SilModel] RETURNS [name: ROPE] ~ {
Get the name of the currently active Sil file for this model.
RETURN [model.activeName];
};
MarkFileAsEdited: PUBLIC PROC [model: SilModel] RETURNS [deletedBuildBoxes: BOOLFALSE] ~ {
Mark this file as edited. If this is a "built" file the build marks will be deleted and the display process should know about this.
IF model.modelIsBuilt THEN {
buildMarksDeleted: ARRAY BuildMarkIndex OF BOOLALL[FALSE];
FOR s: SilObject ← model.fgnd, s.rest WHILE s # NIL DO
FOR i: BuildMarkIndex IN BuildMarkIndex DO
IF NOT buildMarksDeleted[i] AND
buildMarks[i].xMin = s.first.xMin AND
buildMarks[i].yMin = s.first.yMin AND
buildMarks[i].xMax = s.first.xMax AND
buildMarks[i].yMax = s.first.yMax THEN {
buildMarksDeleted[i] ← TRUE;
DeleteObject[model, s];
};
ENDLOOP;
ENDLOOP;
model.modelIsBuilt ← FALSE;
RETURN [TRUE];
};
};
Types for use in Input and Output of Sil Files:
Passwords indicating Sil state:
UnbuiltPassByte1: CHAR ~ '9;
UnbuiltPassByte2: CHAR ~ 'r;
BuiltPassByte1: CHAR ~ '9;
BuiltPassByte2: CHAR ~ 's;
LargePassByte1: CHAR ~ '9;
LargePassByte2: CHAR ~ 'l; -- new password for large format files
SilBlockRef: TYPE ~ REF SilBlock;
SilBlock: TYPE ~ MACHINE DEPENDENT RECORD [
leftChar, rightChar: CHAR,
state: FourBits,
xMin: TwelveBits,
yMin: SixteenBits,
color: FourBits,
xMax: TwelveBits,
font: FourBits,
italic: OneBit,
yMax: ElevenBits
];
SevenBits: TYPE ~ [0..177B];
FourBits: TYPE ~ [0..17B];
TwelveBits: TYPE ~ [0..7777B];
SixteenBits: TYPE ~ [0..177777B];
OneBit: TYPE ~ [0..1];
ElevenBits: TYPE ~ [0..3777B];
LargeSilBlockRef: TYPE ~ REF LargeSilBlock; --new format for large format files
LargeSilBlock: TYPE ~ MACHINE DEPENDENT RECORD [
leftChar, rightChar: CHAR,
xMin: INTEGER,
yMin: INTEGER,
xMax: INTEGER,
yMax: INTEGER,
color: FourBits,
font: FourBits,
italic: OneBit,
pad: SevenBits ← 0
];
mainElementName: CHAR ~ 377C; --this as name of object means it's part of main picture
FileToInternalRep: PROC [model: SilModel, name: ROPE, relative: BOOLFALSE, macroFile: BOOLFALSE, picFont: LibraryFonts ← 5] ~ {
Read the entire contents of file with name and arrange the component records in the internal representation. If macroFile is FALSE then this file will contain a main picture, and several macro definitions which will be stored in the model provided (picFont will be ignored). If macroFile is TRUE then the main picture will be ignored, and the macro definitions will be stored as pictures in the global variable allModelMacros (the model provided will be ignored). Any old macro definitions which conflict with old ones will be replaced, but old definitions which do not conflict will remain. If relative is true, objects in the main picture will be placed relative to (MarkX, MarkY) instead of (0, 0).
sBlock: SilBlockRef ← NEW[SilBlock];
largesBlock: LargeSilBlockRef ← NEW[LargeSilBlock];
passw1, passw2: CHAR;
GetNextSilObject: PROC [s: STREAM] RETURNS [name: CHAR, sob: SilObject] ~ {
Return the Sil object which appears next in the file being read. Also, return the object's name, which will be ' (space) for main pictures, and a printing character for objects which are part of macro definitions.
ENABLE IO.EndOfStream => ERROR SilKernel.SilError[BadFile];-- send a civilized message if the file ends up lying to us
ReadChar: PROC [] RETURNS [c: CHAR] ~ {
This procedure exists so it can be passed to Rope.FromProc, below.
RETURN [s.GetChar[] ];
};
len: INTEGER;
sobr: SilObjectRec;
TRUSTED {
bytesGot: INT ← s.UnsafeGetBlock[[base: base, startIndex: 0, count: count]];
IF bytesGot # count THEN ERROR SilKernel.SilError[BadFile];
};
IF large THEN {
name ← IF largesBlock.leftChar = mainElementName THEN ' ELSE largesBlock.rightChar;
sobr.xMin ← largesBlock.xMin;
sobr.yMin ← largesBlock.yMin;
sobr.xMax ← largesBlock.xMax;
sobr.yMax ← largesBlock.yMax;
sobr.color ← largesBlock.color;
sobr.font ← largesBlock.font;
sobr.italic ← largesBlock.italic = 1;
}
ELSE {
name ← IF sBlock.leftChar = mainElementName THEN ' ELSE sBlock.rightChar;
sobr.xMin ← sBlock.xMin;
sobr.yMin ← sBlock.yMin;
sobr.xMax ← sBlock.xMax;
sobr.yMax ← sBlock.yMax;
sobr.color ← sBlock.color;
sobr.font ← sBlock.font;
sobr.italic ← sBlock.italic = 1;
};
if you have a string, get its value unless it is a macroFile string, in which case you igonre it by skipping its characters.
IF sobr.font IN InternalRopeFonts THEN { -- this is a string, handle its chars
len ← s.GetChar[] - 0C; -- first byte is length
IF macroFile AND name = ' THEN s.SetIndex[s.GetIndex[] + len] ELSE sobr.value ← Rope.FromProc [len, ReadChar, maxRopeLen];
IF len MOD 2 = 0 THEN --Make sure start next object on a word boundary
[] ← s.GetChar[]; --Flush the extra slot for a Char
};
sob ← LIST[sobr];
};
s: STREAMFS.StreamOpen[name ! FS.Error => ERROR SilKernel.SilError[BadFileName]];
And now, a little array to tell us which macro definitions are new . . .
seen: PACKED ARRAY MacroName OF BOOLALL[FALSE];
markX: INTEGER ← SilDisplayCursors.GetMarkX[]; -- for relative input
markY: INTEGER ← SilDisplayCursors.GetMarkY[];
large: BOOLFALSE;
base: LONG POINTERNIL;
count: INTEGER ← 0;
Right here we are going to read password(s) and do something with it, someday.
passw1 ← s.GetChar[]; passw2 ← s.GetChar[];
IF macroFile = FALSE THEN {
IF (passw1 # UnbuiltPassByte1 AND passw1 # BuiltPassByte1 AND passw1 # LargePassByte1) OR (passw2 # UnbuiltPassByte2 AND passw2 # BuiltPassByte2 AND passw2 # LargePassByte2) THEN {
MessageWindow.Append["Illegal Sil File password, proceeding anyway. ", TRUE];
MessageWindow.Blink[];
model.modelIsBuilt ← FALSE;
}
ELSE model.modelIsBuilt ← (passw1 = BuiltPassByte1 AND passw2 = BuiltPassByte2);
};
large ← passw2=LargePassByte2; --reading large format file
base ← IF large THEN LOOPHOLE[largesBlock] ELSE LOOPHOLE[sBlock]; -- used by GetNextSilObject
count ← IF large THEN SIZE[LargeSilBlock]*Basics.bytesPerWord ELSE SIZE[SilBlock]*Basics.bytesPerWord; -- used by GetNextSilObject
WHILE NOT s.EndOf[] DO
name: CHAR;
sob: SilObject;
[name, sob] ← GetNextSilObject[s];
SELECT TRUE FROM
NOT macroFile AND name = ' => { --Main file object main picture
IF relative THEN { --modify object's position
sob.first.xMin ← sob.first.xMin + markX;
sob.first.yMin ← sob.first.yMin + markY;
sob.first.xMax ← sob.first.xMax + markX;
sob.first.yMax ← sob.first.yMax + markY;
};
IF sob.first.font IN InternalBackgroundBoxFonts THEN model.bkgnd ← JoinObjectLists[model.bkgnd, sob]
ELSE model.fgnd ← JoinObjectLists[model.fgnd, sob];
};
NOT macroFile AND name # ' => { --Main file object macro picture
IF NOT seen[name] THEN { -- new macro def
model.macros[name].def ← NIL;
seen[name] ← TRUE;
model.macros[name].xMin ← sob.first.xMin;
model.macros[name].yMin ← sob.first.yMin;
model.macros[name].xMax ← sob.first.xMax;
model.macros[name].yMax ← sob.first.yMax;
}
ELSE { -- add object to macro
IF sob.first.xMin < model.macros[name].xMin THEN
model.macros[name].xMin ← sob.first.xMin;
IF sob.first.yMin < model.macros[name].yMin THEN
model.macros[name].yMin ← sob.first.yMin;
IF sob.first.xMax > model.macros[name].xMax THEN
model.macros[name].xMax ← sob.first.xMax;
IF sob.first.yMax > model.macros[name].yMax THEN
model.macros[name].yMax ← sob.first.yMax;
};
model.macros[name].def ← JoinObjectLists[model.macros[name].def, sob];
};
macroFile AND name = ' => --Library file main picture object. Ignore.
LOOP;
macroFile AND name # ' => { --Library file macro picture object. Add to global macros.
IF NOT seen[name] THEN {
allModelsMacros[picFont][name].def ← NIL;
seen[name] ← TRUE;
allModelsMacros[picFont][name].xMin ← sob.first.xMin;
allModelsMacros[picFont][name].yMin ← sob.first.yMin;
allModelsMacros[picFont][name].xMax ← sob.first.xMax;
allModelsMacros[picFont][name].yMax ← sob.first.yMax;
}
ELSE { -- add to library file macro picture object
IF sob.first.xMin < allModelsMacros[picFont][name].xMin THEN
allModelsMacros[picFont][name].xMin ← sob.first.xMin;
IF sob.first.yMin < allModelsMacros[picFont][name].yMin THEN
allModelsMacros[picFont][name].yMin ← sob.first.yMin;
IF sob.first.xMax > allModelsMacros[picFont][name].xMax THEN
allModelsMacros[picFont][name].xMax ← sob.first.xMax;
IF sob.first.yMax > allModelsMacros[picFont][name].yMax THEN
allModelsMacros[picFont][name].yMax ← sob.first.yMax;
};
allModelsMacros[picFont][name].def ← JoinObjectLists[allModelsMacros[picFont][name].def, sob];
};
ENDCASE;
ENDLOOP;
s.Close[];
};
InternalRepToFile: PROC [model: SilModel, name: ROPE, clipIt: BOOLFALSE, large: BOOLFALSE, xMin , yMin, xMax, yMax: INTEGER ← 0] RETURNS [wasClipped: BOOLFALSE] ~ {
Create a file with name and place all of the internal data concerning the main picture and the macrodefinitions into it. If clipIt then only output those objects which lie entirely inside the region given. If large, output in the new CedarSil large format.
base: LONG POINTERNIL;
count: INTEGER ← 0;
sBlock: SilBlockRef ← NEW[SilBlock];
largesBlock: LargeSilBlockRef ← NEW[LargeSilBlock];
WriteNextSilObject: PROC [name: CHAR, sobr: SilObjectRec] ~ {
Write a Sil Object to the open file. If it is a main picture object the name will be ' (space),
otherwise the name will be the name of the macro definition that the picture belongs to. If large, write in new large format.
IF large THEN { -- write in new large format
IF name = ' THEN
largesBlock.leftChar ← largesBlock.rightChar ← mainElementName
ELSE {
largesBlock.leftChar ← 0C;
largesBlock.rightChar ← name;
};
largesBlock.xMin ← sobr.xMin;
largesBlock.yMin ← sobr.yMin;
largesBlock.color ← sobr.color;
largesBlock.xMax ← sobr.xMax;
largesBlock.font ← sobr.font;
largesBlock.italic ← IF sobr.italic THEN 1 ELSE 0;
largesBlock.yMax ← sobr.yMax;
}
ELSE { -- same old format
IF name = ' THEN
sBlock.leftChar ← sBlock.rightChar ← mainElementName
ELSE {
sBlock.leftChar ← 0C;
sBlock.rightChar ← name;
};
sBlock.xMin ← sobr.xMin;
sBlock.yMin ← sobr.yMin;
sBlock.color ← sobr.color;
sBlock.xMax ← sobr.xMax;
sBlock.font ← sobr.font;
sBlock.italic ← IF sobr.italic THEN 1 ELSE 0;
sBlock.yMax ← sobr.yMax;
};
TRUSTED { s.UnsafePutBlock[[base: base, startIndex: 0, count: count]];};
Now check to see if a rope should be output
IF sobr.font IN InternalRopeFonts THEN {
len: NAT ← sobr.value.Length[];
s.PutChar[len + 0C];
Output the chars of the rope
s.PutRope[sobr.value];
Make sure start next object on a word boundary
IF len MOD 2 = 0 THEN s.PutChar[0C];
};
};
Open the file.
s: STREAM
FS.StreamOpen[fileName: name, accessOptions: $create, createByteCount: BytesInModel[model, large]
! FS.Error => ERROR SilKernel.SilError[BadFileName]; ];
output the password.
IF large THEN {
s.PutChar[LargePassByte1];
s.PutChar[LargePassByte2];
base ← LOOPHOLE[largesBlock];
count ← SIZE[LargeSilBlock]*Basics.bytesPerWord;
}
ELSE {
base ← LOOPHOLE[sBlock];
count ← SIZE[SilBlock]*Basics.bytesPerWord;
IF model.modelIsBuilt THEN {
s.PutChar[BuiltPassByte1];
s.PutChar[BuiltPassByte2];
}
ELSE {
s.PutChar[UnbuiltPassByte1];
s.PutChar[UnbuiltPassByte2];
};
};
First, handle the case where an unclipped old format file is being written
IF NOT (clipIt OR large) THEN { -- limit the file to the size the old format can handle
clipIt ← TRUE;
xMin ← yMin ← 0;
xMax ← LAST[TwelveBits]; yMax ← LAST[ElevenBits];
};
Now the macro definitions
FOR c: MacroName IN MacroName DO
FOR s: SilObject ← model.macros[c].def, s.rest WHILE s # NIL DO
WriteNextSilObject[c, s.first];
ENDLOOP;
ENDLOOP;
Now the main picture objects
FOR s: SilObject ← model.fgnd, s.rest WHILE s # NIL DO
SELECT TRUE FROM
large AND clipIt => ERROR; --this can happen but shouldn't
(clipIt AND s.first.xMin >= xMin AND s.first.yMin >= yMin AND
s.first.xMax <= xMax AND s.first.yMax <= yMax) => WriteNextSilObject[' , s.first];
NOT clipIt => WriteNextSilObject[' , s.first]; -- this case applies when large=TRUE
ENDCASE => wasClipped ← wasClipped OR TRUE;
ENDLOOP;
FOR s: SilObject ← model.bkgnd, s.rest WHILE s # NIL DO
SELECT TRUE FROM
large AND clipIt => ERROR; --this can happen but shouldn't
(clipIt AND s.first.xMin >= xMin AND s.first.yMin >= yMin AND
s.first.xMax <= xMax AND s.first.yMax <= yMax) => WriteNextSilObject[' , s.first];
NOT clipIt => WriteNextSilObject[' , s.first]; -- this case applies when large=TRUE
ENDCASE => wasClipped ← wasClipped OR TRUE;
ENDLOOP;
Now close the file
s.Close[];
};
BytesInModel: PROC [model: SilModel, large: BOOLEAN] RETURNS [byteLength: INT] ~ {
Return what the current length of the file to be generated from model (writing out the password, main picture, and macro definitions), and, for rope objects, one byte for each character in the rope, as well as a byte for the length. Every object must generate an even number of bytes.
ObjectByteLength: PROC [sobr: SilObjectRec] RETURNS [obLength: INT] ~ {
Return the number of bytes necessary to output this object bytes for every object, and, for rope objects, one byte for each character in the rope, as well as a byte for the length. Every object must generate an even number of bytes.
IF sobr.font IN InternalBoxFonts THEN RETURN[objectSize];
obLength ← Rope.Length[sobr.value] + 1 + objectSize; --rope + length byte + all the rest
IF obLength MOD 2 # 0 THEN obLength ← obLength + 1;
};
objectSize: INTIF large THEN SIZE[LargeSilBlock]*Basics.bytesPerWord ELSE SIZE[SilBlock]*Basics.bytesPerWord;
byteLength ← 2; --For the password
The main picture . . .
FOR s: SilObject ← model.fgnd, s.rest WHILE s # NIL DO
byteLength ← byteLength + ObjectByteLength[s.first];
ENDLOOP;
FOR s: SilObject ← model.bkgnd, s.rest WHILE s # NIL DO
byteLength ← byteLength + ObjectByteLength[s.first];
ENDLOOP;
Now the macro definitions
FOR c: MacroName IN MacroName DO
FOR s: SilObject ← model.macros[c].def, s.rest WHILE s # NIL DO
byteLength ← byteLength + ObjectByteLength[s.first];
ENDLOOP;
ENDLOOP;
};
JoinObjectLists: PROC [base, extension: SilObject] RETURNS [SilObject] ~ {
this routine is more efficient if extension is shorter than base
FOR s: SilObject ← extension, s.rest WHILE s # NIL DO
IF s.rest = NIL THEN {
s.rest ← base;
RETURN [extension];
};
ENDLOOP;
RETURN [base];
};
PushDeleteQueue: PROC [model: SilModel] ~ {
FOR i: DeletesToKeep ← deleteBottom, i - 1 WHILE i > deleteTop DO
model.deleted[i] ← model.deleted[i - 1];
ENDLOOP;
model.deleted[deleteTop] ← NIL;
};
PopDeleteQueue: PROC [model: SilModel] RETURNS [sob: SilObject] ~ {
Could return NIL.
sob ← model.deleted[deleteTop];
FOR i: DeletesToKeep ← deleteTop, i + 1 WHILE i < deleteBottom DO
model.deleted[i] ← model.deleted[i + 1];
ENDLOOP;
model.deleted[deleteBottom] ← NIL;
};
PointInBBox: PROC [x, y: INTEGER, sobr: SilObjectRec] RETURNS [in: BOOL] = INLINE {
Return TRUE if <x, y> is inside the bounding box of sobr (the border of the box counts as inside the box).
RETURN [NOT (y > sobr.yMax OR x > sobr.xMax OR y < sobr.yMin OR x < sobr.xMin)];
};
BoxPerimeter: PROC [sobr: SilObjectRec] RETURNS [perim: INTEGER] = INLINE {
Return the perimeter of the bounding box of sobr.
RETURN[ (sobr.xMax - sobr.xMin)*2 + (sobr.yMax - sobr.yMin)*2 ]
};
ObjectIsSelected: PUBLIC PROC [model: SilModel, sob: SilObject] RETURNS [selected: BOOL] = {
TRUE if sob is already selected. Uses the observation that if an object has a non-NIL selectObj link, then it must be selected. There is a dummy object on the end of the selections so that even the last selected object will have a non-NIL select link.
RETURN[model = allSelection.model AND sob.first.selectObj # NIL];
};
BoundingBoxOfSelection: PROC [] RETURNS [xMin, yMin, xMax, yMax: INTEGER] ~ {
Return the bounding box of the currently selected objects
IF allSelection.numObjects=0 THEN RETURN[0,0,0,0];
xMin ← yMin ← LAST[INTEGER];
xMax ← yMax ← FIRST[INTEGER];
FOR s: SilObject ← allSelection.objects, s.first.selectObj WHILE s # allSelection.lastObject DO {
xMin ← MIN[xMin, s.first.xMin];
yMin ← MIN[yMin, s.first.yMin];
xMax ← MAX[xMax, s.first.xMax];
yMax ← MAX[yMax, s.first.yMax];
};
ENDLOOP;
};
END.