IconEditorIO: 
CEDAR 
PROGRAM
IMPORTS FS, IO, IconRegistry, MessageWindow, Real, RuntimeError, PrincOpsUtils
EXPORTS IconEditorDefs = {  
CouldntLoadIcons: PUBLIC SIGNAL = CODE;
CouldntSaveIcons: PUBLIC SIGNAL = CODE;
ViewerNoGood: PUBLIC SIGNAL = CODE;
PointsMarked: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, type: IconEditorDefs.MarkType] 
RETURNS [
BOOLEAN] = {
IF (handle.mark1.x = 0) AND (handle.mark1.y = 0) AND
(handle.mark2.x = 0) 
AND (handle.mark2.y = 0) 
THEN {
SELECT type 
FROM
rect => MessageWindow.Append["Hold down yellow mouse button and mark a rectangular area first...", TRUE];
line => MessageWindow.Append["Hold down yellow mouse button and control key and mark a line first...", TRUE];
ENDCASE => ERROR;
 
MessageWindow.Blink[];
RETURN[FALSE] 
}
ELSE RETURN[TRUE];
};
 
SaveBitMap: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle] = {
handle.savedBitMap ← handle.icons[LOOPHOLE[handle.currentIC]].bits;
};
 
LayoutBoard: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, bounds: Imager.Box] = {
iconsOnDisplay: IconEditorDefs.Nat ← MIN[handle.numberOfIcons, IconEditorDefs.maxIconsOnDisplay];
handle.numIconsPerRow ← Real.FixI[bounds.xmax/(IconEditorDefs.iconW+IconEditorDefs.distX)];
handle.numIconsPerCol ← Real.FixI[bounds.ymax/(IconEditorDefs.iconH+IconEditorDefs.distY)];
IF handle.numIconsPerRow*handle.numIconsPerCol < iconsOnDisplay 
THEN {
MessageWindow.Append["Icon Editor viewer is too small to display current Icon menu.", TRUE];
SIGNAL ViewerNoGood
};
 
handle.possibleX ← Real.FixI[bounds.xmax - ((iconsOnDisplay + handle.numIconsPerCol - 1) /
handle.numIconsPerCol) * (IconEditorDefs.iconW + IconEditorDefs.distX)];
handle.possibleY ← Real.FixI[bounds.ymax - ((iconsOnDisplay + handle.numIconsPerRow-1) /
handle.numIconsPerRow) * (IconEditorDefs.iconH+IconEditorDefs.distY)];
IF handle.possibleX < IconEditorDefs.minimumUnit * IconEditorDefs.iconW 
AND handle.possibleY < IconEditorDefs.minimumUnit * IconEditorDefs.iconH 
THEN {
MessageWindow.Append["Icon Editor viewer is too small for a decent drawing board.", TRUE];
SIGNAL ViewerNoGood
};
 
IF handle.possibleX <= handle.possibleY 
THEN { 
-- use a vertical layout, preferred if square
handle.boardSize ← MIN[bounds.xmax, handle.possibleY];
handle.numIconsPerCol ← (iconsOnDisplay + handle.numIconsPerRow - 1) /
handle.numIconsPerRow;
handle.icX ← 0;
handle.icY ← Real.FixI[handle.boardSize] + 1
}
 
ELSE { 
-- use a horizontal layout
handle.boardSize ← MIN[bounds.ymax, handle.possibleX];
handle.numIconsPerRow ← (iconsOnDisplay + handle.numIconsPerCol - 1) /
handle.numIconsPerCol;
handle.icX ← Real.FixI[handle.boardSize] + 1;
handle.icY ← 0
};
 
handle.unit ← Real.FixI[handle.boardSize / (IconEditorDefs.intHBound + 1)];
handle.boardSize ← handle.unit * (IconEditorDefs.intHBound + 1);
};
 
GetNewFlavor: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle] 
RETURNS [newFlavor: IconEditorDefs.IconFlavor] = {
newFlavor ← handle.nextFlavor;
handle.nextFlavor ← SUCC[newFlavor];
IF 
LOOPHOLE[handle.nextFlavor, IconEditorDefs.Nat] >= IconEditorDefs.maxStorage 
THEN {
MessageWindow.Append[message: "Sorry- you have exceeded the current limit on number of icons.",
clearFirst: TRUE];
MessageWindow.Blink[];
ERROR
};
 
};
 
LoadIcons: PUBLIC PROC[handle: IconEditorDefs.IconHandle, filename: Rope.ROPE]
RETURNS [nRead: IconEditorDefs.Nat, iconFile: 
IO.
STREAM ← NIL] = 
TRUSTED {
ENABLE IO.Error => GOTO Punt;
badNews: BOOL ← FALSE;
newFlavor: IconEditorDefs.IconFlavor;
handle.nextFlavor ← FIRST[IconEditorDefs.IconFlavor]; -- Initialize this since our icons start w/ the 1st flavor
iconFile ← 
FS.StreamOpen[fileName: filename ! 
FS.Error => {
MessageWindow.Append[filename, TRUE];
MessageWindow.Append[": ", FALSE];
MessageWindow.Append[error.explanation, FALSE];
MessageWindow.Blink[]; badNews ← TRUE; CONTINUE
}];
IF badNews THEN RETURN;
FOR i: INTEGER IN [0..IconEditorDefs.maxStorage) DO handle.icons[i] ← NIL; ENDLOOP;
nRead ← iconFile.GetLength[] / (Basics.bytesPerWord*SIZE[IconEditorDefs.IconFileFormat]);
FOR n: IconEditorDefs.Nat 
IN [0..nRead) 
DO
newFlavor ← GetNewFlavor[handle];
IF handle.icons[LOOPHOLE[newFlavor]] = NIL THEN 
handle.icons[LOOPHOLE[newFlavor]] ← NEW[IconEditorDefs.IconFileFormat];
[] ← iconFile.UnsafeGetBlock[block: [base: LOOPHOLE[handle.icons[LOOPHOLE[newFlavor]]], startIndex: 0, count: SIZE[IconEditorDefs.IconFileFormat]*Basics.bytesPerWord] ! RuntimeError.BoundsFault => GOTO Punt];
ENDLOOP;
 
iconFile.Close[];  -- now close things up
EXITS Punt => SIGNAL CouldntLoadIcons;
};
 
SaveIcons: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, filename: Rope.
ROPE, numberOfIcons: IconEditorDefs.Nat] 
RETURNS [iconFile: 
IO.
STREAM ← NIL] = {
badNews: BOOL ← FALSE;
iconFile ← 
FS.StreamOpen[fileName: filename, accessOptions: create, keep: 2 ! 
FS.Error => {
MessageWindow.Append[filename, TRUE];
MessageWindow.Append[": ", FALSE];
MessageWindow.Append[error.explanation, FALSE];
MessageWindow.Blink[]; badNews ← TRUE; CONTINUE
}];
IF badNews THEN RETURN;
FOR j: IconEditorDefs.Nat 
IN [0..numberOfIcons) 
DO 
iconName: Rope.ROPE;
iconFile.UnsafePutBlock[block: [
LOOPHOLE[handle.icons[
LOOPHOLE[j]]], 0,
SIZE[IconEditorDefs.IconFileFormat]*Basics.bytesPerWord] ! RuntimeError.BoundsFault => GOTO Punt];
TRUSTED {IF PrincOpsUtils.IsBound[LOOPHOLE[IconRegistry.IsRegistered]] AND (iconName ← IconRegistry.IsRegistered[fileName: filename, index: j].name) # NIL THEN IconRegistry.InvalidateCache[iconName]};  -- w.t.
ENDLOOP;
 
iconFile.Flush[]; -- make sure we got everything
iconFile.Close[]; -- now close things up
EXITS Punt => SIGNAL CouldntSaveIcons;
};
 
NormalizeRectangle: 
PUBLIC 
PROC [corner1, corner2: IconEditorDefs.MarkPoint] 
RETURNS [rect: IconEditorDefs.RectangleRec] = {
rect.x ← MIN[corner1.x, corner2.x];
rect.y ← MIN[corner1.y, corner2.y];
rect.w ← ABS[corner2.x-corner1.x] + 1;
rect.h ← ABS[corner2.y-corner1.y] + 1;
};
 
TransferEndPoints: 
PUBLIC 
PROC [endpoint1, endpoint2: IconEditorDefs.MarkPoint] 
RETURNS [line: IconEditorDefs.LineRec] = {
line.x1 ← endpoint1.x; line.y1 ← endpoint1.y;
line.x2 ← endpoint2.x; line.y2 ← endpoint2.y;
};
 
ComputeEndPoints: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, oldLine: IconEditorDefs.LineRec] 
RETURNS [newLine: IconEditorDefs.LineRec] = {
center: IconEditorDefs.Nat;
center ← Real.FixI[handle.unit/2];  -- used to center endpoints of line in grid square
newLine.x1 ← oldLine.x1*handle.unit;  newLine.y1 ← (IconEditorDefs.intHBound- oldLine.y1)*handle.unit;
newLine.x2 ← oldLine.x2*handle.unit;  newLine.y2 ← (IconEditorDefs.intHBound- oldLine.y2)*handle.unit;
IF newLine.x1 > newLine.x2 
THEN { 
newLine.x1 ← newLine.x1 + handle.unit - center; 
newLine.x2 ← newLine.x2 + center
};
 
IF newLine.x2 > newLine.x1 
THEN { 
newLine.x2 ← newLine.x2 + handle.unit - center; 
newLine.x1 ← newLine.x1 + center
};
 
IF newLine.x1 = newLine.x2 
THEN { 
newLine.x1 ← newLine.x2 ← newLine.x2 + center
};
 
IF newLine.y1 > newLine.y2 
THEN { 
newLine.y1 ← newLine.y1 + handle.unit - center; 
newLine.y2 ← newLine.y2 + center
};
 
IF newLine.y2 > newLine.y1 
THEN { 
newLine.y2 ← newLine.y2 + handle.unit - center; 
newLine.y1 ← newLine.y1 + center
};
 
IF newLine.y1 = newLine.y2 
THEN {
newLine.y1 ← newLine.y2 ← newLine.y2 + center
};
 
};
 
ClearIcon: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor] = {
i,j: IconEditorDefs.Nat;
iconToClear: IconEditorDefs.IconRef ← handle.icons[LOOPHOLE[flavor]];
FOR i 
IN IconEditorDefs.IntW 
DO
FOR j 
IN IconEditorDefs.IntH 
DO
iconToClear.bits[j].b[i] ← FALSE;
ENDLOOP;
 
ENDLOOP;
 
};
 
MirrorIcon: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor, rect: IconEditorDefs.RectangleRec] = {
i,j: IconEditorDefs.Nat;
temp: BOOLEAN;
farRight: IconEditorDefs.Nat ← rect.x+rect.w-1;
FOR j 
IN [rect.y..rect.y+rect.h-1] 
DO
FOR i 
IN [0..rect.w/2) 
DO
temp ← handle.icons[LOOPHOLE[flavor]].bits[j].b[rect.x + i];
handle.icons[
LOOPHOLE[flavor]].bits[j].b[rect.x + i] ←
handle.icons[LOOPHOLE[flavor]].bits[j].b[farRight - i];
handle.icons[LOOPHOLE[flavor]].bits[j].b[farRight - i] ← temp;
ENDLOOP;
 
ENDLOOP;
 
};
 
DrawLine: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, line: IconEditorDefs.LineRec, flavor: IconEditorDefs.IconFlavor]= {
factor: INTEGER;
deltaX, deltaY, i, e, x, y: IconEditorDefs.Nat;
deltaX ← ABS[line.x2 - line.x1];
deltaY ← ABS[line.y2 - line.y1];
IF ((line.x1<line.x2) 
AND (line.y2<line.y1)) 
OR ((line.x2<line.x1) 
AND (line.y1<line.y2)) 
THEN {
factor ← -1;
y ← MAX[line.y1, line.y2] 
}
 
ELSE {
factor ← 1;
y ← MIN[line.y1, line.y2] 
};
 
x ← MIN[line.x1, line.x2];
IF deltaX >= deltaY 
THEN {
e ← 2*deltaY - deltaX;
FOR i 
IN [0..deltaX] 
DO
handle.icons[LOOPHOLE[flavor]].bits[y].b[x] ← TRUE;
IF e > 0 
THEN {
IF (y >= 0) AND (y < IconEditorDefs.intHBound) THEN y ← y + factor; -- Just to be safe
e ← e + (2*deltaY - 2*deltaX) 
}
 
ELSE e ← e + 2*deltaY;
IF x < IconEditorDefs.intWBound THEN x ← x + 1;   -- Just to be safe
ENDLOOP;
 
}
 
ELSE {
e ← 2*deltaX - deltaY;
FOR i 
IN [0..deltaY] 
DO
handle.icons[LOOPHOLE[flavor]].bits[y].b[x] ← TRUE;
IF e > 0 
THEN {
IF x < IconEditorDefs.intWBound THEN x ← x + 1; -- Just to be safe
e ← e + (2*deltaX - 2*deltaY) 
}
 
ELSE e ← e + 2*deltaX;
IF (y >= 0) AND (y < IconEditorDefs.intHBound) THEN y ← y + factor;  -- Just to be safe
ENDLOOP;
 
};
 
FreshMarks[handle];
};
 
FreshMarks: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle] = {
handle.mark1.x ← handle.mark1.y ← handle.mark2.x ← handle.mark2.y ← 0;
handle.functionNotApplied ← FALSE;
};
 
WhiteLabel: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor] = {
handle.icons[LOOPHOLE[flavor]].invertLabel ← TRUE;
};
 
BlackLabel: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor] = {
handle.icons[LOOPHOLE[flavor]].invertLabel ← FALSE;
};
 
SetLabel: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor, rect: IconEditorDefs.RectangleRec]= {
i: IconEditorDefs.IconRef ← handle.icons[LOOPHOLE[flavor]];
i.label ← TRUE;
handle.labelRect.x ← i.lx ← rect.x;
i.ly ← IconEditorDefs.iconH - 1 - (rect.y + rect.h);
handle.labelRect.y ← rect.y;
handle.labelRect.w ← i.lw ← rect.w;
handle.labelRect.h ← i.lh ← rect.h;
FreshMarks[handle];
};
 
UnSetLabel: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor]= {
i: IconEditorDefs.IconRef ← handle.icons[LOOPHOLE[flavor]];
i.label ← FALSE;
i.lx ← 0;  i.ly ← 0;
i.lw ← 0; i.lh ← 0;
};
 
SetBlack: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor, rect: IconEditorDefs.RectangleRec] = {
i, j: IconEditorDefs.Nat;
FOR i 
IN [rect.x..rect.x+rect.w-1] 
DO
FOR j 
IN [rect.y..rect.y+rect.h-1] 
DO
handle.icons[LOOPHOLE[flavor]].bits[j].b[i] ← TRUE;
ENDLOOP;
 
ENDLOOP;
 
FreshMarks[handle];
};
 
SetWhite: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor, rect: IconEditorDefs.RectangleRec] = {
i, j: IconEditorDefs.Nat;
iconToClear: IconEditorDefs.IconRef ← handle.icons[LOOPHOLE[flavor]];
FOR i 
IN [rect.x..rect.x+rect.w-1] 
DO
FOR j 
IN [rect.y..rect.y+rect.h-1] 
DO
iconToClear.bits[j].b[i] ← FALSE;
ENDLOOP;
 
ENDLOOP;
 
FreshMarks[handle];
};
 
SetDeskTopGray: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor, rect: IconEditorDefs.RectangleRec] = {
i, j, k: IconEditorDefs.Nat;
indent: IconEditorDefs.Nat ← 0;
SetWhite[handle: handle, flavor: flavor, rect: rect]; --must clear before setting grey bits
FOR j ← 0, j+2 
WHILE j < 
LOOPHOLE[IconEditorDefs.intHBound] 
DO
each gray line is two lines high
FOR k 
IN [0..1] 
DO
paint gray
FOR i ← indent, i+4 WHILE i <= LOOPHOLE[IconEditorDefs.intWBound] DO
IF j IN [rect.y .. rect.y+rect.h) AND i IN [rect.x ..rect.x+rect.w) THEN 
handle.icons[LOOPHOLE[flavor]].bits[j+k].b[i] ← TRUE;
ENDLOOP;     
 
ENDLOOP;
next time do a differnt indentation
indent ← IF indent=0 THEN 2 ELSE 0;
ENDLOOP;
 
FreshMarks[handle];
};
 
SetDarkGray: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor, rect: IconEditorDefs.RectangleRec] = {
i, j: IconEditorDefs.Nat;
indent: IconEditorDefs.Nat ← 0;
SetWhite[handle: handle, flavor: flavor, rect: rect]; --must clear before setting grey bits
FOR j 
IN [rect.y..rect.y+rect.h-1) 
DO
paint gray
FOR i ← (rect.x+indent), i+2 
WHILE i < (rect.x+rect.w-1) 
DO
handle.icons[LOOPHOLE[flavor]].bits[j].b[i] ← TRUE;
ENDLOOP;     
 
next time do a differnt indentation
indent ← IF indent=0 THEN 1 ELSE 0;
ENDLOOP;
 
FreshMarks[handle];
};
 
Invert: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor, rect: IconEditorDefs.RectangleRec] = {
i,j: IconEditorDefs.Nat;
FOR i 
IN [rect.x..rect.x+rect.w-1] 
DO
FOR j 
IN [rect.y..rect.y+rect.h-1] 
DO
handle.icons[
LOOPHOLE[flavor]].bits[j].b[i] ←
IF handle.icons[LOOPHOLE[flavor]].bits[j].b[i] THEN FALSE ELSE TRUE;
ENDLOOP;
 
ENDLOOP;
 
FreshMarks[handle];
};
 
ICRectangle: TYPE = RECORD [xmin, ymin, xmax, ymax: IconEditorDefs.Nat];
GetRectangle: 
PROC[handle: IconEditorDefs.IconHandle] 
RETURNS[ICRectangle] = {
x1, y1, x2, y2: IconEditorDefs.Nat;
x1 ← handle.mark1.x; y1 ← handle.mark1.y;
x2 ← handle.mark2.x; y2 ← handle.mark2.y;
IF x1=0 AND y1=0 AND x2=0 AND y2=0 THEN { x2 ← IconEditorDefs.intWBound; y2 ← IconEditorDefs.intHBound };
RETURN[[xmin: MIN[x1, x2], ymin: MIN[y1, y2], xmax: MAX[x1, x2], ymax: MAX[y1, y2]]];
};
 
MoveRight: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor, slowShift: 
BOOLEAN] = {
numberToMove: IconEditorDefs.Nat ← IF slowShift THEN 1 ELSE 4;
r: ICRectangle = GetRectangle[handle];
FOR k: IconEditorDefs.Nat 
IN [1..numberToMove] 
DO
oldBits: IconEditorDefs.BitArray ← handle.icons[LOOPHOLE[flavor]].bits;
FOR i: IconEditorDefs.Nat 
IN [r.xmin..r.xmax] 
DO
FOR j: IconEditorDefs.Nat 
IN [r.ymin..r.ymax] 
DO
handle.icons[
LOOPHOLE[flavor]].bits[j].b[i] ←
oldBits[j].b[IF i=r.xmin THEN r.xmax ELSE i-1];
ENDLOOP;
 
ENDLOOP;
 
ENDLOOP;
 
};
 
MoveLeft: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor, slowShift: 
BOOLEAN] = {
numberToMove: IconEditorDefs.Nat ← IF slowShift THEN 1 ELSE 4;
r: ICRectangle = GetRectangle[handle];
FOR k: IconEditorDefs.Nat 
IN [1..numberToMove] 
DO
oldBits: IconEditorDefs.BitArray ← handle.icons[LOOPHOLE[flavor]].bits;
FOR i: IconEditorDefs.Nat 
IN [r.xmin..r.xmax] 
DO
FOR j: IconEditorDefs.Nat 
IN [r.ymin..r.ymax] 
DO
handle.icons[
LOOPHOLE[flavor]].bits[j].b[i] ←
oldBits[j].b[IF i=r.xmax THEN r.xmin ELSE i+1];
ENDLOOP;
 
ENDLOOP;
 
ENDLOOP;
 
};
 
MoveUp: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor, slowShift: 
BOOLEAN] = {
numberToMove: IconEditorDefs.Nat ← IF slowShift THEN 1 ELSE 4;
r: ICRectangle = GetRectangle[handle];
FOR k: IconEditorDefs.Nat 
IN [1..numberToMove] 
DO
oldBits: IconEditorDefs.BitArray ← handle.icons[LOOPHOLE[flavor]].bits;
FOR i: IconEditorDefs.Nat 
IN [r.xmin..r.xmax] 
DO
FOR j: IconEditorDefs.Nat 
IN [r.ymin..r.ymax] 
DO
handle.icons[
LOOPHOLE[flavor]].bits[j].b[i] ←
oldBits[IF j=r.ymax THEN r.ymin ELSE j+1].b[i];
ENDLOOP;
 
ENDLOOP;
 
ENDLOOP;
 
};
 
MoveDown: 
PUBLIC 
PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor, slowShift: 
BOOLEAN] = {
numberToMove: IconEditorDefs.Nat ← IF slowShift THEN 1 ELSE 4;
r: ICRectangle = GetRectangle[handle];
FOR k: IconEditorDefs.Nat 
IN [1..numberToMove] 
DO
oldBits: IconEditorDefs.BitArray ← handle.icons[LOOPHOLE[flavor]].bits;
FOR i: IconEditorDefs.Nat 
IN [r.xmin..r.xmax] 
DO
FOR j: IconEditorDefs.Nat 
IN [r.ymin..r.ymax] 
DO
handle.icons[
LOOPHOLE[flavor]].bits[j].b[i] ←
oldBits[IF j=r.ymin THEN r.ymax ELSE j-1].b[i];
ENDLOOP;
 
ENDLOOP;
 
ENDLOOP;
 
};
 
}.