G2dZoomCmdImpl.mesa
Copyright Ó 1988, 1992 by Xerox Corporation. All rights reserved.
Bloomenthal, August 25, 1992 2:24 pm PDT
Glassner, September 14, 1989 1:14:41 pm PDT
DIRECTORY BasicTime, CedarProcess, Commander, Controls, Convert, Draw2d, FileNames, FS, G2dBasic, G2dPopUp, G2dSpline, G2dTool, G2dZoom, Icons, Imager, ImagerBackdoor, ImagerMaskCache, ImagerSample, ImagerTransformation, IO, MessageWindow, Process, Real, RealFns, Rope, Vector2, ViewerClasses, ViewerOps, ViewerPrivate, ViewerTools;
G2dZoomCmdImpl: CEDAR PROGRAM
IMPORTS BasicTime, CedarProcess, Controls, Convert, Draw2d, FileNames, FS, G2dPopUp, G2dSpline, G2dTool, G2dZoom, Icons, ImagerBackdoor, ImagerMaskCache, ImagerSample, ImagerTransformation, IO, MessageWindow, Process, Real, RealFns, Rope, ViewerOps, ViewerPrivate, ViewerTools
~ BEGIN
Types
PixelMap:     TYPE ~ Imager.PixelMap;
SampleMap:    TYPE ~ ImagerSample.SampleMap;
Transformation:   TYPE ~ ImagerTransformation.Transformation;
ROPE:      TYPE ~ Rope.ROPE;
VEC:      TYPE ~ Vector2.VEC;
Viewer:     TYPE ~ ViewerClasses.Viewer;
Key:      TYPE ~ REF KeyRep;
KeyRep:     TYPE ~ RECORD [keyNum: NAT ¬ 0, time, dx, dy, scale, rotate: REAL ¬ 0];
KeySequence:   TYPE ~ REF KeySequenceRep;
KeySequenceRep:  TYPE ~ RECORD [
length:       CARDINAL ¬ 0,
element:       SEQUENCE maxLength: CARDINAL OF Key
];
Frame:     TYPE ~ RECORD [bw, a, b: SampleMap];
FrameSequence:   TYPE ~ REF FrameSequenceRep;
FrameSequenceRep:  TYPE ~ RECORD [
length:       CARDINAL ¬ 0,
element:       SEQUENCE maxLength: CARDINAL OF Frame
];
Data:      TYPE ~ REF DataRep;
DataRep:     TYPE ~ RECORD [
operation:       ROPE ¬ NIL,
stop, busy:      BOOL ¬ FALSE,
keys:        KeySequence ¬ NIL,
nFrame:       INT ¬ -1,
keyNum, selectedKey:   NAT ¬ 0,
spline:       G2dSpline.Spline1dSequence ¬ NIL,
knots:        G2dBasic.RealSequence ¬ NIL,
ticks:        SampleMap ¬ NIL,
frames:       FrameSequence ¬ NIL,
xMove, yMove:     Controls.Control ¬ NIL,
scale, rotate:      Controls.Control ¬ NIL,
speed, frameNum:    Controls.Control ¬ NIL,
outerData:      Controls.OuterData ¬ NIL,
outer, viewer, graphics:  Viewer ¬ NIL
];
Pan/Zoom Command
ZoomCmd: Commander.CommandProc ~ {
d: Data ¬ NEW[DataRep];
d.xMove ¬ Controls.NewControl[name: "XMove", proc: XForm, type: hSlider, clientData: d, min: -10., max: 10.0, init: 0, x: 70, y: 15, w: 150, detents: LIST[[, 0.0]]];
d.yMove ¬ Controls.NewControl[name: "YMove", proc: XForm, type: vSlider, clientData: d, min: -10., max: 10.0, init: 0, x: 30, y: 15, h: 150, detents: LIST[[, 0.0]]];
d.scale ¬ Controls.NewControl[name: "Scale", proc: XForm, type: vSlider, taper: exp, clientData: d, min: 1.0/32.0, max: 32., init: 1, x: 235, y: 15, h: 150, detents: LIST[[, 0.5], [, 1.0], [, 2.0]]];
d.rotate ¬ Controls.NewControl[name: "Rotate", proc: XForm, type: dial, clientData: d, max: 360.0, init: 0.0, x: 100, y: 65, w: 100, h: 100, detents: LIST[[, 0.0]]];
d.speed ¬ Controls.NewControl[name: "Speed", type: vSlider, clientData: d, min: 1, max: 40, init: 30, x: 300, y: 15, h: 150, precision: 0, detents: LIST[[, 30]]];
d.frameNum ¬ Controls.NewControl[name: "Frame", proc: NFrame, type: vSlider, clientData: d, min: 0, max: 0, init: 0, x: 345, y: 15, h: 150, precision: 0];
d.outerData ¬ Controls.OuterViewer[
name: "2dZoom",
column: right,
buttons: LIST[
Controls.ClickButton[name: "Play:", row: 0],
Controls.ClickButton[name:"STOP", proc:Button, clientData:d, guarded:TRUE, row:0, x:45],
Controls.ClickButton["Interp", Button, d, 0],
Controls.ClickButton["Replay", Button, d, 0],
Controls.ClickButton["Cycle", Button, d, 0],
Controls.ClickButton["BackForth", Button, d, 0],
Controls.ClickButton["Time", Button, d, 0],
Controls.ClickButton[name: "Keys:", row: 1],
Controls.ClickButton["Add", Button, d, 1, 45],
Controls.ClickButton["Del", Button, d, 1],
Controls.ClickButton["Del All", Button, d, 1],
Controls.ClickButton["Write", Button, d, 1],
Controls.ClickButton["Read", Button, d, 1],
Controls.ClickButton[name: " ", row: 2],
Controls.ClickButton[name:"Get Viewer", proc:Button, clientData:d, guarded:TRUE, row:2, x:45],
Controls.ClickButton[name: "Reset", proc: Button, clientData: d, guarded: TRUE, row: 2],
Controls.ClickButton["IP Out", Button, d, 2],
Controls.ClickButton["AISs Out", Button, d, 2],
Controls.ClickButton["Help!", Button, d, 2]],
controls: LIST[d.rotate, d.scale, d.xMove, d.yMove, d.speed, d.frameNum],
typescriptHeight: 18,
graphicsHeight: 105,
drawProc: DrawProc,
mouseProc: MouseProc,
destroyProc: DestroyProc,
clientData: d
];
d.outer ¬ d.outerData.parent;
d.graphics ¬ d.outerData.graphics;
Enlarge the font cache for better performance:
[] ← ImagerMaskCache.GetNamedCache[$Bitmap, 8000];  -- this doesn't work
[] ¬ ImagerMaskCache.GetNamedCache[$Bitmap];
[] ¬ ViewerPrivate.SetCreator[NIL];
TRUSTED {Process.Detach[FORK CycleIcon[d.outer]]};
};
CycleIcon: PROC [v: Viewer] ~ {
PaintIcon: PROC [i: NAT] ~ {
v.icon ¬ icons[i];
ViewerOps.PaintViewer[v, all];
Process.Pause[Process.MsecToTicks[100]];
};
v.icon ¬ icons[0];
WHILE NOT v.destroyed DO
IF forwardIcon
THEN FOR i: NAT IN [0..nIcons) DO IF v.iconic THEN PaintIcon[i]; ENDLOOP
ELSE FOR i: NAT DECREASING IN [0..nIcons) DO IF v.iconic THEN PaintIcon[i]; ENDLOOP;
forwardIcon ¬ NOT forwardIcon;
Process.Pause[Process.SecondsToTicks[7]];
ENDLOOP;
};
forwardIcon: BOOL ¬ TRUE;
DestroyProc: Controls.DestroyProc ~ {
d: Data ¬ NARROW[clientData];
d.stop ¬ TRUE;
G2dZoom.Release[d.viewer];
};
Message: PROC [rope: ROPE, clear, blink: BOOL ¬ TRUE] ~ {
MessageWindow.Append[Rope.Concat["\t\t\t", rope], clear];
IF blink THEN MessageWindow.Blink[];
};
Display
XFromTime: PROC [d: Data, time: REAL] RETURNS [INTEGER] ~ {
t: REAL ¬ IF d.keys = NIL OR d.keys[0].time = d.keys[d.keys.length-1].time
THEN 0.5 ELSE (time-d.keys[0].time)/REAL[d.keys[d.keys.length-1].time-d.keys[0].time];
RETURN[Real.Round[25+t*(d.graphics.cw-50)]];
};
TimeFromX: PROC [d: Data, x: INTEGER] RETURNS [REAL] ~ {
k: KeySequence ¬ d.keys;
RETURN[SELECT TRUE FROM
k = NIL OR k.length = 0 => 0.0,
k.length = 1 OR d.graphics.cw = 0 => k[0].time,
ENDCASE => (REAL[x-25]/(d.graphics.cw-50))*(k[k.length-1].time-k[0].time)+k[0].time];
};
Repaint: PROC [d: Data, whatChanged: REF ¬ NIL] ~ {
ViewerOps.PaintViewer[d.graphics, client, FALSE, whatChanged];
};
DrawProc: Controls.DrawProc ~ {
BufferTicks: PROC [op: {save, restore}] ~ {
Save: PROC [pixelMap: PixelMap] ~ {d.ticks ¬ ImagerSample.Copy[pixelMap[0]]};
Restore: PROC [pixelMap: PixelMap] ~ {ImagerSample.Transfer[pixelMap[0], d.ticks]};
r: Imager.Rectangle ¬ [0, 0, d.graphics.ww, d.graphics.wh];
ImagerBackdoor.AccessBufferRectangle[context, IF op = save THEN Save ELSE Restore, r];
};
DrawTicks: PROC ~ {
IF d.keys # NIL AND d.keys.length > 0 AND d.graphics.cw # 0 THEN {
KeyX: PROC [n: NAT] RETURNS [x: REAL] ~ {x ¬ XFromTime[d, d.keys[n].time]};
lastT: REAL ¬ d.keys[d.keys.length-1].time;
totalT: REAL ¬ lastT-d.keys[0].time;
dT: REAL ¬ totalT*(80.0/d.graphics.cw);
Draw2d.Line[context, [KeyX[0], 55], [KeyX[d.keys.length-1], 55]];
FOR n: NAT IN [0..d.keys.length) DO
x: REAL ¬ KeyX[n];
Draw2d.Line[context, [x, 55], [x, 70]];
Draw2d.Label[context, [x-10, 75], IO.PutFR1["K%g", IO.int[d.keys[n].keyNum]]];
ENDLOOP;
IF dT # 0.0 THEN {
exp: INTEGER ¬ Real.Floor[RealFns.Log[10.0, totalT]];
div: REAL ¬ IF exp IN [-2..2] THEN 1.0 ELSE RealFns.Power[10.0, exp];
FOR t: REAL ¬ d.keys[0].time, t+dT WHILE t <= lastT DO
tt: REAL ¬ IF lastT-t < dT THEN lastT ELSE t;
x: REAL ¬ XFromTime[d, tt];
Draw2d.Line[context, [x, 40], [x, 55]];
Draw2d.Label[context, [x-10, 30], IO.PutFR1["%3.2f", IO.real[tt/div]]];
ENDLOOP;
Draw2d.Label[context, [d.graphics.cw/2-40, 10], Rope.Concat[
"Time", IF div = 1.0 THEN NIL ELSE IO.PutFR1[" (X %6.4f)", IO.real[div]]]];
};
};
};
d: Data ¬ NARROW[clientData];
SELECT whatChanged FROM
$Ticks => BufferTicks[restore];
ENDCASE => {
Draw2d.DoWithBuffer[context, DrawTicks];
BufferTicks[save];
};
IF d.frames # NIL AND d.frames.length > 1 AND d.nFrame # -1 THEN {
totalT: REAL ¬ d.keys[d.keys.length-1].time-d.keys[0].time;
IF totalT # 0.0 THEN {
x: REAL ¬ XFromTime[d, d.keys[0].time+totalT*(REAL[d.nFrame]/(d.frames.length-1))];
Draw2d.Line[context, [x, 55], [x, 85]];
Draw2d.Label[context, [x-10, 90], IO.PutFR1["F%g", IO.int[d.nFrame ]]];
};
};
};
MouseProc: Controls.MouseProc ~ {
d: Data ¬ NARROW[clientData];
time: REAL ¬ TimeFromX[d, mouse.pos.x];
IF d.keys = NIL OR d.keys.length = 0 THEN RETURN;
SELECT mouse.state FROM
down => {
close: REAL ¬ Real.LargestNumber;
FOR n: NAT IN [0..d.keys.length) DO
IF ABS[d.keys[n].time-time] > close THEN LOOP;
close ¬ ABS[d.keys[n].time-time];
d.selectedKey ¬ n;
ENDLOOP;
};
held => {
d.keys[d.selectedKey].time ¬ time;
OrderKeys[d.keys];
Repaint[d];
};
ENDCASE;
};
Interpolation
Interp: PROC [d: Data] ~ {
IF d.keys # NIL AND d.keys.length > 0 THEN {
reply: ROPE ¬ Controls.TypescriptRead[d.outerData.typescript, "Number of frames: "];
IF reply # NIL THEN {
nFrames: CARDINAL ¬ Convert.IntFromRope[reply ! Convert.Error => GOTO Bad];
IF nFrames > 2 THEN {
time0: REAL ¬ d.keys[0].time;
time1: REAL ¬ d.keys[d.keys.length-1].time;
d.frameNum.max ¬ nFrames-1;
IF d.frames = NIL OR d.frames.maxLength < nFrames
THEN d.frames ¬ NEW[FrameSequenceRep[nFrames]];
d.frames.length ¬ nFrames;
FOR n: INT IN [0..nFrames) WHILE NOT d.stop DO
SetTime[d, time0+(n/REAL[nFrames-1])*(time1-time0)];
Buffer[d, save, n];
ENDLOOP;
IF d.stop THEN d.frames.length ¬ 0;
};
};
EXITS Bad => Message["Bad value"];
};
};
OrderKeys: PROC [keys: KeySequence] ~ {
Success: PROC RETURNS [BOOL ¬ TRUE] ~ {
FOR n: NAT IN [1..keys.length) DO
IF keys[n-1].time > keys[n].time THEN {
temp: Key ¬ keys[n];
keys[n] ¬ keys[n-1];
keys[n-1] ¬ temp;
RETURN[FALSE];
};
ENDLOOP;
};
DO IF Success[] THEN EXIT; ENDLOOP;
};
GetInterpReady: PROC [d: Data] ~ {
IF d.spline = NIL OR d.spline.maxLength < d.keys.length
THEN d.spline ¬ NEW[G2dSpline.Spline1dSequenceRep[d.keys.length-1]];
IF d.knots = NIL OR d.knots.maxLength < d.keys.length
THEN d.knots ¬ NEW[G2dBasic.RealSequenceRep[d.keys.length]];
d.knots.length ¬ d.keys.length;
};
SetTime: PROC [d: Data, time: REAL] ~ {
SetFromKey: PROC [k: Key] ~ {rotate ¬ k.rotate; scale ¬ k.scale; dx ¬ k.dx; dy ¬ k.dy};
rotate, scale, dx, dy: REAL;
GetInterpReady[d];
FOR n: NAT IN [0..d.keys.length) DO
IF d.keys[n].time < time THEN LOOP;
IF n = 0
THEN SetFromKey[d.keys[0]]
ELSE {
Field: TYPE ~ {rotate, scale, dx, dy};
GetValue: PROC [k: Key, f: Field] RETURNS [r: REAL] ~ {
r ¬ SELECT f FROM dx => k.dx, dy => k.dy, scale => k.scale, ENDCASE => k.rotate;
};
GetReal: PROC [f: Field] RETURNS [r: REAL] ~ {
FOR n: NAT IN [0..d.keys.length) DO
d.knots[n] ¬ GetValue[d.keys[n], f];
ENDLOOP;
d.spline ¬ G2dSpline.Interpolate1d[d.knots, d.spline, TRUE];
r ¬ G2dSpline.Position1d[d.spline[n-1], t];
};
t: REAL ¬ (time-d.keys[n-1].time)/(d.keys[n].time-d.keys[n-1].time);
rotate ¬ GetReal[rotate];
scale ¬ GetReal[scale];
dx ¬ GetReal[dx];
dy ¬ GetReal[dy];
EXIT;
};
REPEAT
FINISHED => SetFromKey[d.keys[d.keys.length-1]];
ENDLOOP;
DoTransform[d, dx, dy, scale, rotate, FALSE];
};
Buffer: PROC [d: Data, op: {save, restore}, nFrame: NAT] ~ {
Remaining (not = available) real memory: 512*VMInternal.freePages (SHARES VmInternal)
v: Viewer ← d.viewer;
vt: Terminal.Virtual ← Terminal.Current[];
fb: Terminal.FrameBuffer ← IF v.column = color
THEN Terminal.GetColorFrameBufferA[vt]
ELSE Terminal.GetBWFrameBuffer[vt];
nBits: NATSELECT fb.bitsPerPixel FROM 1 => 1, 8 => 8, ENDCASE => 24;
fbB: Terminal.FrameBuffer IF nBits = 24 THEN Terminal.GetColorFrameBufferB[vt] ELSE NIL;
box: ImagerSample.Box ← [[fb.height-v.wy-v.ch, v.wx+v.cx], [fb.height-v.wy, v.ww]];
map: SampleMap ← ImagerSample.MapFromFrameBuffer[fb];
mapB: SampleMap ← --IF fbB # NIL THEN ImagerSample.MapFromFrameBuffer[fbB] ELSE-- NIL;
clipped: SampleMap ← ImagerSample.Clip[map, box];
clippedB: SampleMap ← IF mapB # NIL THEN ImagerSample.Clip[mapB, box] ELSE NIL;
d.nFrame ← nFrame;
Repaint[d, $Ticks];
IF op = save
THEN d.frames[nFrame] ← SELECT nBits FROM
1   => [ImagerSample.Copy[clipped], NIL, NIL],
8   => [NIL, ImagerSample.Copy[clipped], NIL],
ENDCASE => [NIL, ImagerSample.Copy[clipped], ImagerSample.Copy[clippedB]]
ELSE {
f: Frame ← d.frames[nFrame];
IF (SELECT nBits FROM 1 => f.bw = NIL, 8 => f.b # NIL, ENDCASE => f.b = NIL) THEN {
d.viewer ← NIL;
Message["Viewer disagrees: please reselect a viewer"];
RETURN;
};
SELECT nBits FROM
1 => {
Terminal.WaitForBWVerticalRetrace[vt];
ImagerSample.Transfer[clipped, f.bw];
};
8 => ImagerSample.Transfer[clipped, f.a];
ENDCASE => {
ImagerSample.Transfer[clipped, f.a];
ImagerSample.Transfer[clippedB, f.b];
};
};
};
Button Parsing
Button: Controls.ClickProc ~ {
d: Data ¬ NARROW[clientData];
d.operation ¬ parent.name;
SELECT TRUE FROM
Rope.Equal["STOP", parent.name] => IF d.busy THEN d.stop ¬ TRUE;
d.busy => Message["Tool is busy!"];
ENDCASE => {
d.busy ¬ TRUE;
[] ¬ CedarProcess.Fork[Operation, d];
};
};
Operation: CedarProcess.ForkableProc ~ {
Eq: PROC [r: ROPE] RETURNS [b: BOOL] ~ {b ¬ Rope.Equal[r, d.operation, FALSE]};
d: Data ¬ NARROW[data];
oldName: ROPE ¬ d.outer.name;
d.outer.name ¬ Rope.Concat[oldName, "\t[Busy]"];
ViewerOps.PaintViewer[d.outer, caption];
SELECT TRUE FROM
Eq["Interp"]   => Interp[d];
Eq["Replay"]   => Play[d, replay];
Eq["Cycle"]   => Play[d, cycle];
Eq["BackForth"]  => Play[d, backforth];
Eq["Time"]   => Time[d];
Eq["Add"]   => AddKey[d];
Eq["Del"]    => DelKey[d];
Eq["Del All"]  => DelAllKeys[d];
Eq["Write"]   => WriteKey[d];
Eq["Read"]   => ReadKey[d];
Eq["Get Viewer"] => GetViewer[d];
Eq["Reset"]   => ResetViewer[d];
Eq["IP Out"]   => IPOut[d];
Eq["AISs Out"]  => AISOut[d];
Eq["Help!"]   => Help[];
ENDCASE;
d.outer.name ¬ oldName;
ViewerOps.PaintViewer[d.outer, caption];
d.stop ¬ d.busy ¬ FALSE;
};
Key Operations
AddKey: PROC [d: Data] ~ {
reply: ROPE ¬ Controls.TypescriptRead[d.outerData.typescript, "Time = "];
IF reply # NIL THEN {
time: REAL ¬ Convert.RealFromRope[reply ! Convert.Error => GOTO Bad];
key: Key ¬ NEW[KeyRep];
key­ ¬ [d.keyNum, time, d.xMove.value, d.yMove.value, d.scale.value, d.rotate.value];
d.keyNum ¬ d.keyNum+1;
IF d.keys = NIL THEN d.keys ¬ NEW[KeySequenceRep[1]];
IF d.keys.length = d.keys.maxLength THEN { -- enlarge the sequence:
old: KeySequence ¬ d.keys;
d.keys ¬ NEW[KeySequenceRep[MAX[Real.Ceiling[1.3*old.maxLength], 3]]];
FOR i: NAT IN [0..old.length) DO d.keys[i] ¬ old[i]; ENDLOOP;
d.keys.length ¬ old.length;
};
d.keys[d.keys.length] ¬ key;
d.keys.length ¬ d.keys.length+1;
OrderKeys[d.keys];
Repaint[d];
EXITS Bad => Message["Bad value"];
};
};
DelAllKeys: PROC [d: Data] ~ {
d.keys.length ¬ 0;
IF d.frames # NIL THEN d.frames.length ¬ 0;
Repaint[d];
};
DelKey: PROC [d: Data] ~ {
IF d.keys.length = 0
THEN Message["No keys to delete"]
ELSE {
reply: ROPE ¬ Controls.TypescriptRead[d.outerData.typescript, "Key # "];
IF reply # NIL THEN {
keyNum: NAT ¬ Convert.IntFromRope[reply ! Convert.Error => GOTO Bad];
FOR n: NAT IN [0..d.keys.length) DO
IF d.keys[n].keyNum # keyNum THEN LOOP;
FOR nn: NAT IN [n..d.keys.length-1) DO
d.keys[nn] ¬ d.keys[nn+1];
ENDLOOP;
d.keys.length ¬ d.keys.length-1;
EXIT;
REPEAT
FINISHED => Message["No such key"];
ENDLOOP;
IF d.frames # NIL THEN d.frames.length ¬ 0;
Repaint[d];
EXITS Bad => Message["Bad value"];
};
};
};
WriteKey: PROC [d: Data] ~ {
fileName: ROPE ¬ Controls.TypescriptReadFileName[d.outerData.typescript];
IF fileName # NIL THEN {
out: IO.STREAM ¬ FS.StreamOpen[FileNames.ResolveRelativePath[fileName], create];
G2dZoom.WriteTransform[d.viewer, out];
IO.Close[out];
};
};
ReadKey: PROC [d: Data] ~ {
fileName: ROPE ¬ Controls.TypescriptReadFileName[d.outerData.typescript];
IF fileName # NIL THEN {
in: IO.STREAM;
in ¬ FS.StreamOpen[FileNames.ResolveRelativePath[fileName] ! FS.Error => CONTINUE];
IF in = NIL
THEN Message["Can't read file", FALSE, FALSE]
ELSE {
m: Transformation ¬ G2dZoom.ReadTransform[in];
f: ImagerTransformation.FactoredTransformation ¬ ImagerTransformation.Factor[m];
IO.Close[in];
DoTransform[d, f.t.x, f.t.y, f.s.x, f.r1];
};
};
};
Play Operations
NFrame: Controls.ControlProc ~ {
d: Data ¬ NARROW[control.clientData];
IF d.frameNum.max < 2 OR d.busy OR
(control.whatChanged # $TypedIn AND control.mouse.state = up) OR
Real.Round[control.valuePrev] = Real.Round[control.value] THEN RETURN;
IF d.frames = NIL OR control.value NOT IN [0..d.frames.length)
THEN Message["Bad value"]
ELSE Buffer[d, restore, Real.Round[control.value]];
};
Play: PROC [d: Data, op: {replay, cycle, backforth}] ~ {
One: PROC ~ {
period: REAL ¬ 1000000.0/d.speed.value;  -- in microseconds
nextTime: REAL ¬ BasicTime.PulsesToMicroseconds[BasicTime.GetClockPulses[]]+period;
Inner: PROC [n: NAT] ~ {
WHILE BasicTime.PulsesToMicroseconds[BasicTime.GetClockPulses[]] < nextTime DO
ENDLOOP;
nextTime ¬ nextTime+period;
Buffer[d, restore, n];
};
IF forward
THEN FOR n: NAT IN [0..d.frames.length) WHILE NOT d.stop DO Inner[n]; ENDLOOP
ELSE FOR n: NAT DECREASING IN [0..d.frames.length) WHILE NOT d.stop DO
Inner[n];
ENDLOOP;
IF op = backforth THEN forward ¬ NOT forward;
};
forward: BOOL ¬ TRUE;
IF d.frames = NIL OR d.viewer = NIL THEN RETURN;
IF op = replay THEN One[] ELSE WHILE NOT d.stop DO One[]; ENDLOOP;
};
Time: PROC [d: Data] ~ {
IF d.keys # NIL AND d.keys.length > 0 THEN {
r: ROPE ¬ Controls.TypescriptRead[d.outerData.typescript, "Time: "];
IF r # NIL THEN SetTime[d, Convert.RealFromRope[r ! Convert.Error => GOTO Bad]];
EXITS Bad => Message["Bad value"];
};
};
Miscellaneous Operations
GetViewer: PROC [d: Data] ~ {G2dZoom.Hijack[d.viewer ¬ ViewerTools.GetSelectedViewer[]]};
ResetViewer: PROC [d: Data] ~ {
G2dZoom.Reset[d.viewer];
Controls.Reset[d.xMove, d.yMove, d.scale, d.rotate];
};
Help: PROC ~ {G2dPopUp.Help["2dZoom", left]};
IPOut: PROC [d: Data] ~ {
fileName: ROPE ¬ Controls.TypescriptReadFileName[d.outerData.typescript];
IF fileName # NIL THEN G2dZoom.IPOut[d.viewer, fileName];
};
AISOut: PROC [d: Data] ~ {
base: ROPE ¬ Controls.TypescriptReadFileName[d.outerData.typescript];
IF base # NIL THEN FOR n: NAT IN [0..d.frames.length) WHILE NOT d.stop DO
name: ROPE ¬ IO.PutFR["%g.%g.ais", IO.rope[base], IO.int[n]];
f: Frame ¬ d.frames[n];
IF f.b # NIL THEN {Message["Sorry, can't write 24 bpp images right now"]; RETURN};
AISExtras.AISFromSampleMap[name, IF f.bw = NIL THEN f.a ELSE f.bw];
ENDLOOP;
};
Transformation
XForm: Controls.ControlProc ~ {
d: Data ¬ NARROW[control.clientData];
IF d.busy OR (control.whatChanged # $TypedIn AND control.mouse.state = up) THEN RETURN;
d.nFrame ¬ -1;  -- no longer within an interpolation or playback
G2dZoom.Transform[d.viewer, [d.xMove.value, d.yMove.value], d.scale.value, d.rotate.value];
};
DoTransform: PROC [d: Data, dx, dy, scale, rotate: REAL, fork: BOOL ¬ TRUE] ~ TRUSTED {
Controls.SetSliderDialValue[d.rotate, rotate];
Controls.SetSliderDialValue[d.scale, scale];
Controls.SetSliderDialValue[d.xMove, dx];
Controls.SetSliderDialValue[d.yMove, dy];
G2dZoom.Transform[d.viewer, [dx, dy], scale, rotate, FALSE];
IF fork
THEN Process.Detach[FORK ViewerOps.PaintViewer[d.viewer, client]]
ELSE ViewerOps.PaintViewer[d.viewer, client];
};
Start Code
nIcons: NAT ~ 8;
icons: ARRAY [0..nIcons) OF Icons.IconFlavor;
FOR i: NAT IN [0..nIcons) DO
icons[i] ¬ Icons.NewIconFromFile["G2dUser.icons", i ! FS.Error => EXIT];
ENDLOOP;
G2dTool.Register["Zoom", ZoomCmd, "Zoom around a viewer"];
END.