FILE: BiScrollersImpl2.Mesa, from [Ivy]<Spreitzer>Viewing>BiScrollers.DF
Last Edited by: Spreitzer, July 21, 1984 12:28:34 pm PDT
one of two implementations of BiScrollers
DIRECTORY
Cursors, TIPUser, Icons, InputFocus, ViewerClasses, ViewerOps,
Graphics, Geom2D, BiScrollers, RealFns, Real, Buttons;
BiScrollersImpl2: CEDAR MONITOR
IMPORTS Graphics, Cursors, TIPUser, InputFocus, VO:ViewerOps,
Geom2D, RealFns, Real, Buttons
EXPORTS BiScrollers =
BEGIN OPEN BiScrollers;
Number: TYPE = Geom2D.Number;
ViewerClass: TYPE = ViewerClasses.ViewerClass;
Axis: TYPE = Geom2D.Axis;
Vec: TYPE = Geom2D.Vec;
Area: TYPE = Geom2D.Area;
AreaRef: TYPE = REF Area;
BiScroller: PUBLIC TYPE = REF BiScrollerObject;
BiScrollerObject: PUBLIC TYPE = RECORD [
class: BiScrollerClass,
clientData: REF ANY,
viewer: Viewer ← NIL,
children: Child ← NIL,
t, u: Transform ← Geom2D.id,
h, v: Viewer ← NIL,  --horizontal and vertical BiScrollBars--
s, r: Viewer ← NIL,   --scale and rotate Knobs--
hv, sr, a: Buttons.Button, --hor/vert, scale/rot, & all reset buttons--
cl, cr, cb, ct: INTEGER ← 0, --limits of subclass's clipping region--
cw, ch: INTEGER ← 0  --last time we looked in the viewer--
];
BiScrollerClass: PUBLIC TYPE = REF BiScrollerClassRec;
BiScrollerClassRec: PUBLIC TYPE = RECORD [
viewerClass: ViewerClass,
notify: ViewerClasses.NotifyProc,
paint: ViewerClasses.PaintProc,
extrema: ExtremaProc,
init: ViewerClasses.InitProc,
mayStretch: BOOLEAN];
Child: TYPE = REF ChildObject;
ChildObject: TYPE = RECORD [next: Child, where: Vec, it: Viewer];
Range: TYPE = REF RangeObject;
RangeObject: TYPE = RECORD [min, max: Number];
BiScrollBar: TYPE = REF BiScrollBarObject;
BiScrollBarObject: TYPE = RECORD [viewer: Viewer ← NIL,
parent: BiScroller,
axis: Axis,
cursors: ARRAY State OF Cursors.CursorType];
State: TYPE = {Idle, Increase, Decrease, Random, Reset};
Knob: TYPE = REF KnobObject;
KnobObject: TYPE = RECORD [viewer: Viewer ← NIL,
parent: BiScroller,
axis: Axis,
newTransform: NewTransformProc,
resetTransform: ResetTransformProc,
deltaTransform: DeltaTransformProc,
cursors: ARRAY State OF Cursors.CursorType,
last: Number ← 0];
NewTransformProc: TYPE = PROC [old: Transform, parm: Number, cx, cy: Number]
RETURNS [new: Transform];
ResetTransformProc: TYPE = PROC [old: Transform, cx, cy: Number]
RETURNS [new: Transform];
DeltaTransformProc: TYPE = PROC [old: Transform, delta: INTEGER, cx, cy: Number]
RETURNS [new: Transform];
awake: BOOLEANFALSE; --true while we have CapturedButtons
indent: INTEGER ← 11;  --width of BiScrollBars
reset: Cursors.CursorType ← bullseye;
grayl: CARDINAL = 11110B;
graym: CARDINAL = 122645B;
grayh: CARDINAL = 102041B;
NewBiScrollerClass: PUBLIC PROC [
flavor: ATOM,
extrema: ExtremaProc,
notify: ViewerClasses.NotifyProc ← NIL,
paint: ViewerClasses.PaintProc ← NIL,
modify: ViewerClasses.ModifyProc ← NIL,
destroy: ViewerClasses.DestroyProc ← NIL,
copy: ViewerClasses.CopyProc ← NIL,
set: ViewerClasses.SetProc ← NIL,
get: ViewerClasses.GetProc ← NIL,
init: ViewerClasses.InitProc ← NIL,
finish: LIST OF REF ANYNIL,
Passed to Notify when mouse leaves an Activated BiScroller (never happens here)
save: ViewerClasses.SaveProc ← NIL,
tipTable: TIPUser.TIPTable ← NIL,
icon: Icons.IconFlavor ← document,
cursor: Cursors.CursorType ← textPointer,
mayStretch: BOOLEANTRUE]
RETURNS [bsc: BiScrollerClass] =
BEGIN
vc: ViewerClass ← NEW [ViewerClasses.ViewerClassRec ← [
flavor: flavor,
notify: NotifyBiScroller,
paint: PaintBiScroller,
modify: modify,
destroy: destroy,
copy: copy,
set: set,
get: get,
save: save,
tipTable: tipTable,
icon: icon,
cursor: cursor]];
VO.RegisterViewerClass[flavor: flavor, class: vc];
bsc ← NEW [BiScrollerClassRec ← [
viewerClass: vc,
notify: notify,
paint: paint,
extrema: extrema,
init: init,
mayStretch: mayStretch]];
END;
CreateBiScroller: PUBLIC PROC [class: BiScrollerClass,
info: ViewerClasses.ViewerRec ← [],
paint: BOOLEANTRUE] RETURNS [new: BiScroller] =
BEGIN
new ← NEW[BiScrollerObject ← [class: class,
clientData: info.data,
t: Geom2D.id]];
info.data ← new;
new.viewer ← VO.CreateViewer[flavor: class.viewerClass.flavor,
info: info, paint: FALSE];
MakeBorders[new];
new.t ← Geom2D.id.Translate[new.viewer.cw/2, new.viewer.ch/2];
new.u ← new.t.Inverse[];
[] ← ComputeClientBounds[new, FALSE];
IF class.init # NIL THEN class.init[new.viewer];
IF paint THEN VO.PaintViewer[viewer: new.viewer, hint: all];
END;
Destroy: PUBLIC PROC [bs: BiScroller] RETURNS [BiScroller] =
BEGIN
VO.DestroyViewer[bs.viewer];
bs.viewer ← NIL;
RETURN [NIL];
END;
AddChild: PUBLIC PROC [to: BiScroller, what: Viewer,
x, y: REAL ← 0, useTheseCoords: BOOLEANFALSE,
paint: BOOLEANTRUE] =
BEGIN
my: Vec;
c: Child ← NEW [ChildObject ← [next: to.children,
it: what,
where: IF useTheseCoords THEN [x, y]
ELSE [what.wx, what.wy] ]];
to.children ← c;
my ← to.t.MapVec[c.where];
VO.MoveViewer[viewer: what, x: RI[my.x], y: RI[my.y],
w: what.ww, h: what.wh, paint: paint];
END;
DeleteChild: PUBLIC PROC [of: BiScroller, who: Viewer] =
BEGIN
Filter: PROC [c: Child] RETURNS [Child] =
{IF c = NIL THEN RETURN [NIL];
IF c.it = who THEN RETURN [c.next];
c.next ← Filter[c.next];
RETURN [c]};
of.children ← Filter[of.children];
END;
IsBiScroller: PUBLIC PROC [ra: REF ANY] RETURNS [BOOLEAN] =
{RETURN [ISTYPE[ra, BiScroller]]};
NarrowToBiScroller: PUBLIC PROC [ra: REF ANY] RETURNS [BiScroller] =
{RETURN [NARROW[ra]]};
QuaViewer: PUBLIC PROC [bs: BiScroller] RETURNS [Viewer] =
{RETURN [bs.viewer]};
QuaBiScroller: PUBLIC PROC [v: Viewer] RETURNS [BiScroller] =
{RETURN [NARROW[v.data]]};
ViewerIsABiScroller: PUBLIC PROC [v: Viewer] RETURNS [BOOLEAN] =
{RETURN [ISTYPE[v.data, BiScroller]]};
ClientDataOf: PUBLIC PROC [bs: BiScroller] RETURNS [REF ANY] =
{RETURN [bs.clientData]};
BoundaryOf: PUBLIC PROC [bs: BiScroller] RETURNS [VecList] =
BEGIN
RETURN [Geom2D.MapVecs[bs.u,
LIST[[bs.cl, bs.cb],
[bs.cr, bs.cb],
[bs.cr, bs.ct],
[bs.cl, bs.ct]]]];
END;
BoundaryExtrema: PUBLIC PROC [bs: BiScroller, direction: Vec] RETURNS [min, max: Vec] =
BEGIN
vl: VecList ← BoundaryOf[bs];
e: Geom2D.ExtremaRec ←
direction.Extreme[vl.first,
direction.Extreme[vl.rest.first,
direction.Extreme[vl.rest.rest.first,
direction.StartExtreme[vl.rest.rest.rest.first]]]];
RETURN [e.minV, e.maxV];
END;
ComputeClientBounds: PROC [bs: BiScroller, paint: BOOLEAN] RETURNS [news: BOOLEAN] =
BEGIN
bs.cl ← indent;
bs.cb ← indent;
make scrollbars stretch all the way across
IF (news ← bs.cw # bs.viewer.cw OR bs.ch # bs.viewer.ch) THEN
BEGIN
bs.cw ← bs.viewer.cw;
bs.ch ← bs.viewer.ch;
bs.cr ← bs.cw - indent;
bs.ct ← bs.ch - indent;
VO.MoveViewer[bs.h, bs.cl, 0, bs.cr-bs.cl, indent, paint];
VO.MoveViewer[bs.v, 0, bs.cb, indent, bs.ct-bs.cb, paint];
VO.MoveViewer[bs.s, bs.cl, bs.ct, bs.cr-bs.cl, indent, paint];
VO.MoveViewer[bs.r, bs.cr, bs.cb, indent, bs.ct-bs.cb, paint];
VO.MoveViewer[bs.a, 0, bs.ct, indent, indent, paint];
VO.MoveViewer[bs.hv, 0, 0, indent, indent, paint];
VO.MoveViewer[bs.sr, bs.cr, bs.ct, indent, indent, paint];
END
ELSE BEGIN
bs.cr ← bs.cw - indent;
bs.ct ← bs.ch - indent;
END;
END;
MakeBorders: PROC [bs: BiScroller] =
BEGIN
bs.h ← VO.CreateViewer[flavor: $BiScrollBarX, info: [
name: "X scrolling", parent: bs.viewer,
wx: indent, wy: 0, ww: bs.viewer.cw-2*indent, wh: indent,
data: NEW[BiScrollBarObject ← [parent: bs, axis: X, cursors: [
Increase: scrollRight,
Decrease: scrollLeft,
Random: pointUp,
Reset: reset,
Idle: scrollLeftRight]]],
scrollable: FALSE, border: FALSE], paint: FALSE];
bs.v ← VO.CreateViewer[flavor: $BiScrollBarY, info: [
name: "Y scrolling", parent: bs.viewer,
wx: 0, wy: indent, ww: indent, wh: bs.viewer.ch-2*indent,
data: NEW[BiScrollBarObject ← [parent: bs, axis: Y, cursors: [
Increase: scrollUp,
Decrease: scrollDown,
Random: pointRight,
Reset: reset,
Idle: scrollUpDown]]],
scrollable: FALSE, border: FALSE], paint: FALSE];
bs.s ← VO.CreateViewer[flavor: $ScaleKnob, info: [
name: "Scale Knob", parent: bs.viewer,
wx: indent, wy: bs.viewer.ch-indent, ww: bs.viewer.cw-2*indent, wh: indent,
data: NEW [KnobObject ← [parent: bs, axis: X,
cursors: [
Idle: scrollLeftRight,
Random: pointDown,
Reset: reset,
Increase: pointRight,
Decrease: pointLeft],
newTransform: NewScale,
resetTransform: ResetScale,
deltaTransform: DeltaScale]],
scrollable: FALSE, border: FALSE], paint: FALSE];
bs.r ← VO.CreateViewer[flavor: $RotateKnob, info: [
name: "Rotate Knob", parent: bs.viewer,
wx: bs.viewer.cw-indent, wy: indent, ww: indent, wh: bs.viewer.ch-2*indent,
data: NEW [KnobObject ← [parent: bs, axis: Y,
cursors: [
Idle: scrollUpDown,
Random: pointLeft,
Reset: reset,
Increase: pointUp,
Decrease: pointDown],
newTransform: NewRotation,
resetTransform: ResetRotation,
deltaTransform: DeltaRotation]],
scrollable: FALSE, border: FALSE], paint: FALSE];
bs.a ← Buttons.Create[info: [name: "", parent: bs.viewer,
wx: 0, wy: bs.viewer.ch-indent, ww: indent, wh: indent],
proc: ResetAll, clientData: bs,
documentation: "I reset the scaling, rotation, and offsets"];
bs.hv ← Buttons.Create[info: [name: "", parent: bs.viewer,
wx: 0, wy: 0, ww: indent, wh: indent],
proc: ResetOffsets, clientData: bs,
documentation: "I reset the offsets"];
bs.sr ← Buttons.Create[info: [name: "", parent: bs.viewer,
wx: bs.viewer.cw-indent, wy: bs.viewer.ch-indent, ww: indent, wh: indent],
proc: ResetSR, clientData: bs,
documentation: "I reset the scaling and rotation"];
END;
NotifyBiScroller: ViewerClasses.NotifyProc =
BEGIN
bs: BiScroller ← NARROW[self.data];
i, o, l: LIST OF REF ANYNIL;
IF bs.class.notify = NIL THEN RETURN;
FOR i ← input, i.rest WHILE i # NIL DO
l ← IF l = NIL THEN o ← CONS[i.first, NIL] ELSE l.rest ← CONS[i.first, NIL];
WITH l.first SELECT FROM
z: TIPUser.TIPScreenCoords =>
{cc: ClientCoords ← NEW [Vec ← bs.u.MapVec[[z.mouseX, z.mouseY]]];
l.first ← cc};
ENDCASE;
ENDLOOP;
bs.class.notify[self, o];
END;
PaintBiScroller: ViewerClasses.PaintProc =
BEGIN
bs: BiScroller ← NARROW[self.data];
Graphics.ClipBox[context, [bs.cl, bs.cb, bs.cr, bs.ct]];
Graphics.Translate[context, bs.t.e, bs.t.f];
Graphics.Concat[context, bs.t.a, bs.t.b, bs.t.c, bs.t.d];
bs.class.paint[self, context, whatChanged, clear];
END;
ResetAll: Buttons.ButtonProc =
BEGIN
bs: BiScroller ← NARROW[clientData];
IF (mouseButton = red) THEN Fit[bs]
ELSE ChangeTransform[bs, [1, 0, 0, 1, 0, 0]];
END;
Fit: PUBLIC PROC [bs: BiScroller, paint: BOOLEANTRUE, mayStretch: BOOLEANFALSE] =
BEGIN
xmin, ymin, xmax, ymax: Vec;
dx, dy, sx, sy: Number;
[xmin, xmax] ← bs.class.extrema[bs.clientData, [1, 0]];
[ymin, ymax] ← bs.class.extrema[bs.clientData, [0, 1]];
dx ← xmax.x - xmin.x; dy ← ymax.y - ymin.y;
sx ← IF ABS[dx] = 0 THEN 1 ELSE (bs.cr-bs.cl)/dx;
sy ← IF ABS[dy] = 0 THEN 1 ELSE (bs.ct-bs.cb)/dy;
IF NOT mayStretch THEN sx ← sy ← MIN[sx, sy];
ChangeTransform[bs, [sx, 0, 0, sy,
(bs.cr+bs.cl)/2 - sx*(xmax.x+xmin.x)/2,
(bs.ct+bs.cb)/2 - sy*(ymax.y+ymin.y)/2], paint];
END;
ResetOffsets: Buttons.ButtonProc =
BEGIN
bs: BiScroller ← NARROW[clientData];
IF (mouseButton = red) THEN
BEGIN
xmin, xmax, ymin, ymax: Number;
[xmin, xmax] ← GetLimits[bs, X];
[ymin, ymax] ← GetLimits[bs, Y];
ChangeTransform[bs, bs.t.Translate[(bs.cl+bs.cr)/2-(xmin+xmax)/2,
(bs.ct+bs.cb)/2-(ymin+ymax)/2]];
END
ELSE ChangeTransform[bs, [bs.t.a, bs.t.b, bs.t.c, bs.t.d, 0, 0]];
END;
ResetSR: Buttons.ButtonProc =
BEGIN
bs: BiScroller ← NARROW[clientData];
s: Number;
v: Vec ← [(bs.cl+bs.cr)/2, (bs.ct+bs.cb)/2];
u: Transform;
u ← bs.t.Translate[-v.x, -v.y];
s ← u.a*u.d - u.b*u.c;
u ← IF ABS[s] < 0.0001 THEN [1, 0, 0, 1, 0, 0]
ELSE [1, 0, 0, 1, (u.e*u.d-u.f*u.c)/s, (u.f*u.a-u.e*u.b)/s];
ChangeTransform[bs, u.Translate[v.x, v.y]];
END;
InitBiScrollBar: ViewerClasses.InitProc =
BEGIN
bsb: BiScrollBar ← NARROW[self.data];
bsb.viewer ← self;
END;
PaintBiScrollBar: ViewerClasses.PaintProc =
BEGIN
bsb: BiScrollBar ← NARROW[self.data];
r: Range;
Do: PROCEDURE [c: CARDINAL, xmin, ymin, xmax, ymax: Number] =
{Graphics.SetStipple[context, c];
Graphics.DrawBox[context, [xmin, ymin, xmax, ymax]]};
IF whatChanged # NIL THEN
BEGIN
r ← NARROW[whatChanged];
SELECT bsb.axis FROM
X => {Do[grayh, 0, 0, r.min*self.cw, self.ch];
Do[graym, r.min*self.cw, 0, r.max*self.cw, self.ch];
Do[grayl, r.max*self.cw, 0, self.cw, self.ch]};
Y => {Do[grayh, 0, 0, self.cw, r.min*self.ch];
Do[graym, 0, r.min*self.ch, self.cw, r.max*self.ch];
Do[grayl, 0, r.max*self.ch, self.cw, self.ch]};
ENDCASE => ERROR;
END;
END;
GetLimits: PROC [bs: BiScroller, axis: Axis]
RETURNS [pmin, pmax: Number] =
BEGIN
norm, min, max: Vec;
SELECT axis FROM
X => norm ← [bs.t.d, -bs.t.b];
Y => norm ← [-bs.t.c, bs.t.a];
ENDCASE => ERROR;
[min, max] ← bs.class.extrema[bs.clientData, norm];
min ← bs.t.MapVec[min];
max ← bs.t.MapVec[max];
SELECT axis FROM
X => RETURN [min.x, max.x];
Y => RETURN [min.y, max.y];
ENDCASE => ERROR;
END;
NotifyBiScrollBarProc: TYPE = PROC [bsb: BiScrollBar, bs: BiScroller,
mouse: TIPUser.TIPScreenCoords, input: LIST OF REF ANY]
RETURNS [LIST OF REF ANY];
WakeUpBiScrollBar: PROC [bsb: BiScrollBar, bs: BiScroller, to: State, indicate: BOOLEAN] =
BEGIN
awake ← TRUE;
Cursors.SetCursor[bsb.viewer.class.cursor ← bsb.cursors[to]];
IF indicate THEN
BEGIN
r: Range ← NEW[RangeObject];
beginW, endW, beginZ, endZ, deltaW: Number;
SELECT bsb.axis FROM
X => {[beginW, endW] ← GetLimits[bs, X]; beginZ ← bs.cl; endZ ← bs.cr};
Y => {[beginW, endW] ← GetLimits[bs, Y]; beginZ ← bs.cb; endZ ← bs.ct};
ENDCASE => ERROR;
deltaW ← endW - beginW;
IF ABS[deltaW] # 0 THEN r^ ← [
MAX[0.0, MIN[1.0, (beginZ - beginW)/deltaW]],
MAX[0.0, MIN[1.0, (endZ - beginW)/deltaW]]]
ELSE r^ ← [
IF beginW > beginZ THEN 0.0 ELSE 1.0,
IF beginW < endZ THEN 1.0 ELSE 0.0];
VO.PaintViewer[viewer: bsb.viewer, hint: client, clearClient: FALSE,
whatChanged: r];
END;
InputFocus.CaptureButtons[proc: bsb.viewer.class.notify, tip: bsb.viewer.tipTable,
viewer: bsb.viewer];
END;
Sleep: PROC [bsb: BiScrollBar] =
BEGIN
Cursors.SetCursor[bsb.viewer.class.cursor ← bsb.cursors[Idle]];
InputFocus.ReleaseButtons[];
awake ← FALSE;
END;
IncreaseBiScrollBar: NotifyBiScrollBarProc =
BEGIN
IF NOT awake THEN WakeUpBiScrollBar[bsb, bs, Increase, TRUE];
IF input.first = $Idle THEN NULL
ELSE IF input.first = $Doit THEN
BEGIN
Sleep[bsb];
Move[bs, SELECT bsb.axis FROM
X => [bsb.viewer.cw - mouse.mouseX, 0],
Y => [0, bsb.viewer.ch - mouse.mouseY],
ENDCASE => ERROR];
END
ELSE ERROR;
RETURN [input.rest];
END;
DecreaseBiScrollBar: NotifyBiScrollBarProc =
BEGIN
IF NOT awake THEN WakeUpBiScrollBar[bsb, bs, Decrease, TRUE];
IF input.first = $Idle THEN NULL
ELSE IF input.first = $Doit THEN
BEGIN
Sleep[bsb];
Move[bs, SELECT bsb.axis FROM
X => [-mouse.mouseX, 0],
Y => [0, -mouse.mouseY],
ENDCASE => ERROR];
END
ELSE ERROR;
RETURN [input.rest];
END;
ThumbBiScrollBar: NotifyBiScrollBarProc =
BEGIN
IF NOT awake THEN WakeUpBiScrollBar[bsb, bs, Random, TRUE];
IF input.first = $Idle THEN NULL
ELSE IF input.first = $Doit THEN
BEGIN
cmin, cmax: Number;
Foo: PROCEDURE [low, high, mouse: Number] RETURNS [Number] =
{RETURN [(low+high)/2 - (cmin + (mouse-low)*(cmax-cmin)/(high-low))]};
SELECT bsb.axis FROM
X => {[cmin, cmax] ← GetLimits[bs, X];
Sleep[bsb];
Move[bs, [Foo[bs.cl, bs.cr, mouse.mouseX], 0]]};
Y => {[cmin, cmax] ← GetLimits[bs, Y];
Sleep[bsb];
Move[bs, [0, Foo[bs.cb, bs.ct, mouse.mouseY]]]};
ENDCASE => ERROR;
END
ELSE ERROR;
RETURN [input.rest];
END;
ResetBiScrollBar: NotifyBiScrollBarProc =
BEGIN
IF NOT awake THEN WakeUpBiScrollBar[bsb, bs, Reset, FALSE];
IF input.first = $Idle THEN NULL
ELSE IF input.first = $Doit THEN
BEGIN
nu: Transform ← bs.t;
SELECT bsb.axis FROM
X => nu.e ← 0;
Y => nu.f ← 0;
ENDCASE => ERROR;
Sleep[bsb];
ChangeTransform[bs, nu];
END
ELSE ERROR;
RETURN [input.rest];
END;
Move: PROCEDURE [bs: BiScroller, by: Vec] =
BEGIN
ChangeTransform[bs, bs.t.Translate[by.x, by.y]];
END;
NotifyBiScrollBar: ViewerClasses.NotifyProc =
BEGIN ENABLE UNWIND => InputFocus.ReleaseButtons[];
bsb: BiScrollBar ← NARROW[self.data];
bs: BiScroller ← bsb.parent;
mouse: TIPUser.TIPScreenCoords;
IF ComputeClientBounds[bs, TRUE] THEN RETURN;
WHILE input # NIL DO
WITH input.first SELECT FROM
x: ATOM => SELECT x FROM
$Increase => input ← IncreaseBiScrollBar[bsb, bs, mouse, input.rest];
$Decrease => input ← DecreaseBiScrollBar[bsb, bs, mouse, input.rest];
$Random => input ← ThumbBiScrollBar[bsb, bs, mouse, input.rest];
$Reset => input ← ResetBiScrollBar[bsb, bs, mouse, input.rest];
ENDCASE => ERROR;
z: TIPUser.TIPScreenCoords => BEGIN
v: Viewer;
c: BOOLEAN;
mouse ← z;
IF awake THEN
BEGIN
[v, c] ← VO.MouseInViewer[mouse];
IF v # self OR NOT c THEN
{Sleep[bsb];
VO.PaintViewer[self, client, TRUE, NIL];
RETURN};
END;
input ← input.rest;
END;
ENDCASE => ERROR;
ENDLOOP;
END;
NewScale: NewTransformProc =
BEGIN
s: Number ← parm * 2;
IF parm < 0.001 THEN RETURN [old];
new ← old.Translate[-cx, -cy].ScaleT[s].Translate[cx, cy];
END;
ResetScale: ResetTransformProc =
BEGIN
s: Number ← RealFns.SqRt[ABS[old.a*old.d - old.b*old.c]];
IF ABS[s] = 0 THEN RETURN [old];
new ← old.Translate[-cx, -cy].ScaleT[1.0/s].Translate[cx, cy];
END;
DeltaScale: DeltaTransformProc =
BEGIN
RETURN [old.Translate[-cx, -cy].ScaleT[SELECT delta FROM
-1 => 1.0/2.0,
1 => 2.0,
ENDCASE => ERROR].Translate[cx, cy]];
END;
NewRotation: NewTransformProc =
BEGIN
RETURN [old.Translate[-cx, -cy].RotateDegrees[(parm-0.5)*360].Translate[cx, cy]];
END;
ResetRotation: ResetTransformProc =
BEGIN
s: Number ← RealFns.SqRt[ABS[old.a*old.d - old.b*old.c]];
IF ABS[s] = 0 THEN RETURN [[1, 0, 0, 1, old.e, old.f]];
new ← old.Translate[-cx, -cy];
new ← [s, 0, 0, s, new.e, new.f];
new ← new.Translate[cx, cy];
END;
DeltaRotation: DeltaTransformProc =
BEGIN
RETURN [old.Translate[-cx, -cy].RotateBy90s[delta].Translate[cx, cy]];
END;
InitKnob: ViewerClasses.InitProc =
BEGIN
k: Knob ← NARROW[self.data];
k.viewer ← self;
END;
PaintKnob: ViewerClasses.PaintProc =
BEGIN
IF whatChanged = NIL THEN RETURN;
WITH whatChanged SELECT FROM
a: AreaRef => {Graphics.SetStipple[context, graym];
[] ← Graphics.SetPaintMode[context, invert];
Graphics.DrawBox[context, a^]};
ENDCASE => ERROR;
END;
SleepKnob: PROC [k: Knob] =
{Cursors.SetCursor[k.viewer.class.cursor ← k.cursors[Idle]];
InputFocus.ReleaseButtons[];
awake ← FALSE};
WakeKnob: PROC [k: Knob, bs: BiScroller, state: State] =
BEGIN
awake ← TRUE;
Cursors.SetCursor[k.viewer.class.cursor ← k.cursors[state]];
InputFocus.CaptureButtons[proc: k.viewer.class.notify, tip: k.viewer.tipTable,
viewer: k.viewer];
END;
KnobNotifyProc: TYPE = PROC [k: Knob, bs: BiScroller,
mouse: TIPUser.TIPScreenCoords, input: LIST OF REF ANY]
RETURNS [LIST OF REF ANY];
ResetKnob: KnobNotifyProc =
BEGIN
IF NOT awake THEN WakeKnob[k, bs, Reset];
IF input.first = $Idle THEN RETURN [input.rest];
IF input.first # $Doit THEN ERROR;
SleepKnob[k];
ChangeTransform[bs, k.resetTransform[bs.t, (bs.cl+bs.cr)/2, (bs.ct+bs.cb)/2]];
RETURN [input.rest];
END;
SetKnob: KnobNotifyProc =
BEGIN
max, mark: Number;
Foo: PROC [xa, xb, ya, yb: Number] =
BEGIN
a: AreaRef ← NEW[Area];
a.xmin ← MIN[xa, xb];
a.xmax ← MAX[xa, xb];
a.ymin ← MIN[ya, yb];
a.ymax ← MAX[ya, yb];
VO.PaintViewer[viewer: k.viewer, hint: client,
clearClient: FALSE, whatChanged: a];
END;
IF NOT awake THEN
{WakeKnob[k, bs, Random];
k.last ← SELECT k.axis FROM
X => k.viewer.cw/2,
Y => k.viewer.ch/2,
ENDCASE => ERROR};
SELECT k.axis FROM
X => {max ← k.viewer.cw; mark ← mouse.mouseX;
Foo[mark, k.last, 0, indent]};
Y => {max ← k.viewer.ch; mark ← mouse.mouseY;
Foo[0, indent, mark, k.last]};
ENDCASE => ERROR;
k.last ← mark;
IF input.first = $Idle THEN RETURN [input.rest];
IF input.first # $Doit THEN ERROR;
SleepKnob[k];
ChangeTransform[bs, k.newTransform[bs.t, mark/max, (bs.cl+bs.cr)/2, (bs.ct+bs.cb)/2]];
RETURN [input.rest];
END;
UpKnob: KnobNotifyProc =
BEGIN
IF NOT awake THEN WakeKnob[k, bs, Increase];
IF input.first = $Idle THEN RETURN [input.rest];
IF input.first # $Doit THEN ERROR;
SleepKnob[k];
ChangeTransform[bs, k.deltaTransform[bs.t, 1, (bs.cl+bs.cr)/2, (bs.ct+bs.cb)/2]];
RETURN [input.rest];
END;
DownKnob: KnobNotifyProc =
BEGIN
IF NOT awake THEN WakeKnob[k, bs, Decrease];
IF input.first = $Idle THEN RETURN [input.rest];
IF input.first # $Doit THEN ERROR;
SleepKnob[k];
ChangeTransform[bs, k.deltaTransform[bs.t, -1, (bs.cl+bs.cr)/2, (bs.ct+bs.cb)/2]];
RETURN [input.rest];
END;
RI: PROC [REAL] RETURNS [INTEGER] = Real.RoundI;
GetTransform: PUBLIC PROC [bs: BiScroller] RETURNS [t: Transform] = {t ← bs.t};
ChangeTransform: PUBLIC PROC [bs: BiScroller, new: Transform, paint: BOOLEANTRUE] =
BEGIN
Doit: ENTRY PROC [t, u: Transform] = {bs.u ← u; bs.t ← t};
inv: Transform ← new.Inverse[];
FOR c: Child ← bs.children, c.next UNTIL c = NIL DO
nu: Vec;
nu ← new.MapVec[c.where];
VO.EstablishViewerPosition[c.it, RI[nu.x], RI[nu.y], c.it.ww, c.it.wh];
ENDLOOP;
Doit[new, inv];
IF paint THEN VO.PaintViewer[viewer: bs.viewer, hint: client];
END;
NotifyKnob: ViewerClasses.NotifyProc =
BEGIN ENABLE UNWIND => InputFocus.ReleaseButtons[];
mouse: TIPUser.TIPScreenCoords;
k: Knob ← NARROW[self.data];
bs: BiScroller ← k.parent;
WHILE input # NIL DO
WITH input.first SELECT FROM
x: ATOM => SELECT x FROM
$Increase => input ← UpKnob[k, bs, mouse, input.rest];
$Decrease => input ← DownKnob[k, bs, mouse, input.rest];
$Random => input ← SetKnob[k, bs, mouse, input.rest];
$Reset => input ← ResetKnob[k, bs, mouse, input.rest];
ENDCASE => ERROR;
z: TIPUser.TIPScreenCoords => BEGIN
v: Viewer;
c: BOOLEAN;
mouse ← z;
IF awake THEN
BEGIN
[v, c] ← VO.MouseInViewer[mouse];
IF v # self OR NOT c THEN
{SleepKnob[k];
VO.PaintViewer[self, client, TRUE, NIL];
RETURN};
END;
input ← input.rest;
END;
ENDCASE => ERROR;
ENDLOOP;
END;
Setup: PROCEDURE =
BEGIN
VO.RegisterViewerClass[flavor: $BiScrollBarX,
class: NEW [ViewerClasses.ViewerClassRec ← [
flavor: $BiScrollBarX,
init: InitBiScrollBar,
notify: NotifyBiScrollBar,
paint: PaintBiScrollBar,
tipTable: TIPUser.InstantiateNewTIPTable["KnobH.TIP"],
cursor: scrollLeftRight]]];
VO.RegisterViewerClass[flavor: $BiScrollBarY,
class: NEW [ViewerClasses.ViewerClassRec ← [
flavor: $BiScrollBarY,
init: InitBiScrollBar,
notify: NotifyBiScrollBar,
paint: PaintBiScrollBar,
tipTable: TIPUser.InstantiateNewTIPTable["KnobV.TIP"],
cursor: scrollUpDown]]];
VO.RegisterViewerClass[flavor: $ScaleKnob,
class: NEW [ViewerClasses.ViewerClassRec ← [
flavor: $ScaleKnob,
init: InitKnob,
notify: NotifyKnob,
paint: PaintKnob,
tipTable: TIPUser.InstantiateNewTIPTable["KnobH.TIP"],
cursor: scrollLeftRight]]];
VO.RegisterViewerClass[flavor: $RotateKnob,
class: NEW [ViewerClasses.ViewerClassRec ← [
flavor: $RotateKnob,
init: InitKnob,
notify: NotifyKnob,
paint: PaintKnob,
tipTable: TIPUser.InstantiateNewTIPTable["KnobV.TIP"],
cursor: scrollUpDown]]];
END;
Setup[];
END.