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: BOOLEAN ← TRUE;
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:
BOOL ←
FALSE, large:
BOOL ←
FALSE, xMin, yMin, xMax, yMax:
INTEGER ← 0]
RETURNS [wasClipped:
BOOL ←
FALSE] ~ {
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:
BOOL ←
FALSE, name: MacroName ← '!]
RETURNS [SelectionOk:
BOOL ←
TRUE] ~ {
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: INTEGER ← LAST[INTEGER];
InnerProc [model.fgnd];
InnerProc [model.bkgnd];
};
DefineSelectWithBox:
PUBLIC
PROC [model: SilModel, xMin, yMin, xMax, yMax:
INTEGER]
RETURNS [objXMin:
INTEGER ←
LAST[
INTEGER], objYMin:
INTEGER ←
LAST[
INTEGER], objXMax:
INTEGER ←
FIRST[
INTEGER], objYMax:
INTEGER ←
FIRST[
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: BOOL ← FALSE;
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:
BOOL ←
FALSE] ~ {
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:
BOOL ←
FALSE] ~ {
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 BOOL ← ALL[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:
BOOL ←
FALSE, macroFile:
BOOL ←
FALSE, 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:
STREAM ←
FS.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 BOOL ← ALL[FALSE];
markX: INTEGER ← SilDisplayCursors.GetMarkX[]; -- for relative input
markY: INTEGER ← SilDisplayCursors.GetMarkY[];
large: BOOL ← FALSE;
base: LONG POINTER ← NIL;
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:
BOOL ←
FALSE, large:
BOOL ←
FALSE, xMin , yMin, xMax, yMax:
INTEGER ← 0]
RETURNS [wasClipped:
BOOL ←
FALSE] ~ {
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 POINTER ← NIL;
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]; ];
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: INT ← IF 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.