<<>> <> <> <> <> <> <> <> <> DIRECTORY Basics USING [IsBound], FS USING [StreamOpen, Error], Imager USING [Box], IconEditorDefs, IconRegistry USING [IsRegistered, InvalidateCache], IO USING [Close, Error, Flush, GetLength, STREAM, UnsafeGetBlock, UnsafePutBlock], MessageWindow USING [Append, Blink], Real USING [Fix], Rope USING [ROPE], RuntimeError USING [BoundsFault]; IconEditorIO: CEDAR PROGRAM IMPORTS Basics, FS, IO, IconRegistry, MessageWindow, Real, RuntimeError EXPORTS IconEditorDefs ~ { CouldntLoadIcons: PUBLIC SIGNAL = CODE; CouldntSaveIcons: PUBLIC SIGNAL = CODE; ViewerNoGood: PUBLIC SIGNAL = CODE; PointsMarked: PUBLIC PROC [handle: IconEditorDefs.IconHandle, type: IconEditorDefs.MarkType] RETURNS [BOOL ¬ TRUE] ~ { 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] }; }; SaveBitMap: PUBLIC PROC [handle: IconEditorDefs.IconHandle] ~ { handle.savedBitMap ¬ handle.icons[LOOPHOLE[handle.currentIC]].bits; }; LayoutBoard: PUBLIC PROC [handle: IconEditorDefs.IconHandle, bounds: Imager.Box] ~ { iconsOnDisplay: INT16 ¬ MIN[handle.numberOfIcons, IconEditorDefs.maxIconsOnDisplay]; handle.numIconsPerRow ¬ Real.Fix[bounds.xmax/(IconEditorDefs.iconW+IconEditorDefs.distX)]; handle.numIconsPerCol ¬ Real.Fix[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.Fix[bounds.xmax - ((iconsOnDisplay + handle.numIconsPerCol - 1) / handle.numIconsPerCol) * (IconEditorDefs.iconW + IconEditorDefs.distX)]; handle.possibleY ¬ Real.Fix[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 { <> handle.boardSize ¬ MIN[bounds.xmax, handle.possibleY]; handle.numIconsPerCol ¬ (iconsOnDisplay + handle.numIconsPerRow - 1) / handle.numIconsPerRow; handle.icX ¬ 0; handle.icY ¬ Real.Fix[handle.boardSize] + 1 } ELSE { <> handle.boardSize ¬ MIN[bounds.ymax, handle.possibleX]; handle.numIconsPerRow ¬ (iconsOnDisplay + handle.numIconsPerCol - 1) / handle.numIconsPerCol; handle.icX ¬ Real.Fix[handle.boardSize] + 1; handle.icY ¬ 0 }; handle.unit ¬ Real.Fix[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, INT16] >= 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: INT16 ¬ 0, 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[] / (<>SIZE[IconEditorDefs.IconFileFormat]); FOR n: INT16 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: INT16] 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: INT16 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 Basics.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: INT16; center ¬ Real.Fix[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: INT16; 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; }; IconHandle: TYPE ~ IconEditorDefs.IconHandle; IconFlavor: TYPE ~ IconEditorDefs.IconFlavor; RectangleRec: TYPE ~ IconEditorDefs.RectangleRec; LineRec: TYPE ~ IconEditorDefs.LineRec; FlipIcon: PUBLIC PROC [handle: IconHandle, flavor: IconFlavor, rect: RectangleRec] ~ { bottom: INT16 ¬ rect.y+rect.h-1; FOR i: INT16 IN [rect.x..rect.x+rect.w) DO FOR j: INT16 IN [0..rect.h/2) DO temp: BOOL ¬ handle.icons[LOOPHOLE[flavor]].bits[rect.y+j].b[i]; handle.icons[LOOPHOLE[flavor]].bits[rect.y+j].b[i] ¬ handle.icons[LOOPHOLE[flavor]].bits[bottom-j].b[i]; handle.icons[LOOPHOLE[flavor]].bits[bottom-j].b[i] ¬ temp; ENDLOOP; ENDLOOP; }; MirrorIcon: PUBLIC PROC [handle: IconHandle, flavor: IconFlavor, rect: RectangleRec] ~ { farRight: INT16 ¬ rect.x+rect.w-1; FOR j: INT16 IN [rect.y..rect.y+rect.h-1] DO FOR i: INT16 IN [0..rect.w/2) DO temp: BOOL ¬ 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: IconHandle, line: LineRec, flavor: IconFlavor] ~ { factor: INTEGER; deltaX, deltaY, i, e, x, y: INT16; deltaX ¬ ABS[line.x2 - line.x1]; deltaY ¬ ABS[line.y2 - line.y1]; IF ((line.x1= 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: INT16; 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: INT16; 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, firstClearToWhite: BOOL ¬ TRUE] ~ { xmin0: INT16 = (rect.x/4)*4; xmin2: INT16 = ((MAX[rect.x, 2] - 2)/4)*4 + 2; phase: INT16 ¬ rect.y MOD 4; x, y: INT16; IF firstClearToWhite THEN SetWhite[handle: handle, flavor: flavor, rect: rect]; --must clear before setting grey bits FOR y IN [rect.y .. rect.y+rect.h) DO xmin: INT16 = IF phase < 2 THEN xmin0 ELSE xmin2; FOR x ¬ xmin, x+4 WHILE x < rect.x+rect.w DO handle.icons[LOOPHOLE[flavor]].bits[y].b[x] ¬ TRUE; ENDLOOP; IF (phase ¬ phase + 1) = 4 THEN phase ¬ 0; ENDLOOP; FreshMarks[handle]; }; SetDarkGray: PUBLIC PROC [handle: IconEditorDefs.IconHandle, flavor: IconEditorDefs.IconFlavor, rect: IconEditorDefs.RectangleRec] ~ { i, j: INT16; indent: INT16 ¬ 0; SetWhite[handle: handle, flavor: flavor, rect: rect]; --must clear before setting grey bits FOR j IN [rect.y..rect.y+rect.h) DO <> FOR i ¬ (rect.x+indent), i+2 WHILE i < (rect.x+rect.w) DO handle.icons[LOOPHOLE[flavor]].bits[j].b[i] ¬ TRUE; ENDLOOP; <> 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: INT16; 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: INT16]; GetRectangle: PROC[handle: IconEditorDefs.IconHandle] RETURNS[ICRectangle] ~ { x1, y1, x2, y2: INT16; 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: BOOL] ~ { numberToMove: INT16 ¬ IF slowShift THEN 1 ELSE 4; r: ICRectangle = GetRectangle[handle]; FOR k: INT16 IN [1..numberToMove] DO oldBits: IconEditorDefs.BitArray ¬ handle.icons[LOOPHOLE[flavor]].bits; FOR i: INT16 IN [r.xmin..r.xmax] DO FOR j: INT16 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: BOOL] ~ { numberToMove: INT16 ¬ IF slowShift THEN 1 ELSE 4; r: ICRectangle = GetRectangle[handle]; FOR k: INT16 IN [1..numberToMove] DO oldBits: IconEditorDefs.BitArray ¬ handle.icons[LOOPHOLE[flavor]].bits; FOR i: INT16 IN [r.xmin..r.xmax] DO FOR j: INT16 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: BOOL] ~ { numberToMove: INT16 ¬ IF slowShift THEN 1 ELSE 4; r: ICRectangle = GetRectangle[handle]; FOR k: INT16 IN [1..numberToMove] DO oldBits: IconEditorDefs.BitArray ¬ handle.icons[LOOPHOLE[flavor]].bits; FOR i: INT16 IN [r.xmin..r.xmax] DO FOR j: INT16 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: BOOL] ~ { numberToMove: INT16 ¬ IF slowShift THEN 1 ELSE 4; r: ICRectangle = GetRectangle[handle]; FOR k: INT16 IN [1..numberToMove] DO oldBits: IconEditorDefs.BitArray ¬ handle.icons[LOOPHOLE[flavor]].bits; FOR i: INT16 IN [r.xmin..r.xmax] DO FOR j: INT16 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; }; }.