FILE: BiScrollersImpl.Mesa
Last Edited by: Spreitzer, November 10, 1985 5:16:51 pm PST
DIRECTORY BiScrollers, Geom2D, Imager, ImagerBox, ImagerTransformation, 
IO, Menus, MessageWindow, Real, RealFns, Rope, ViewerOps, ViewerTools;
 
BiScrollersImpl: 
CEDAR 
MONITOR
IMPORTS Geom2D, ImagerBox, ImagerTransformation, IO, Menus, MessageWindow, Real, RealFns, Rope, ViewerOps, ViewerTools
EXPORTS BiScrollers
SHARES Menus =
 
BEGIN OPEN BiScrollers;
StyleList: TYPE = LIST OF RECORD [name: ROPE, style: BiScrollerStyle];
bsMenu: PUBLIC Menus.Menu ← Menus.CreateMenu[];
styles: StyleList ← NIL;
defaultStyleName: ROPE ← NIL;
GetStyle: 
PUBLIC 
PROC [name: 
ROPE ← 
NIL] 
RETURNS [style: BiScrollerStyle] = {
IF name = NIL THEN name ← defaultStyleName;
FOR sl: StyleList ← styles, sl.rest 
WHILE sl # 
NIL 
DO
IF sl.first.name.Equal[name] THEN RETURN [sl.first.style]
ENDLOOP;
 
style ← NIL;
};
 
RegisterStyle: 
PUBLIC 
ENTRY 
PROC [name: 
ROPE, style: BiScrollerStyle] = {
ENABLE UNWIND => {};
styles ← CONS[[name, style], styles];
};
 
SetDefaultStyle: 
PUBLIC 
ENTRY 
PROC [name: 
ROPE] 
RETURNS [old: 
ROPE] =
{old ← defaultStyleName; defaultStyleName ← name};
 
IsBiScroller: 
PUBLIC 
PROC [ra: 
REF 
ANY] 
RETURNS [
BOOLEAN] =
{RETURN [ISTYPE[ra, BiScroller]]};
 
NarrowToBiScroller: 
PUBLIC 
PROC [ra: 
REF 
ANY] 
RETURNS [BiScroller] =
{bs: BiScroller ← NARROW[ra]; RETURN [bs]};
 
QuaViewer: 
PUBLIC 
PROC [bs: BiScroller, inner: 
BOOL ← 
FALSE] 
RETURNS [Viewer] =
{RETURN [bs.style.QuaViewer[bs, inner]]};
 
QuaBiScroller: 
PUBLIC 
PROC [v: Viewer] 
RETURNS [BiScroller] =
{bs: BiScroller ← NARROW[v.data]; RETURN [bs]};
 
ViewerIsABiScroller: 
PUBLIC 
PROC [v: Viewer] 
RETURNS [
BOOLEAN] =
{RETURN [ISTYPE[v.data, BiScroller]]};
 
ClientDataOf: 
PUBLIC 
PROC [bs: BiScroller] 
RETURNS [
REF 
ANY] =
{RETURN [bs.style.ClientDataOf[bs]]};
 
ClientDataOfViewer: 
PUBLIC 
PROC [v: Viewer] 
RETURNS [
REF 
ANY] =
{bs: BiScroller ← NARROW[v.data]; RETURN [bs.style.ClientDataOf[bs]]};
 
ViewportExtrema: 
PUBLIC 
PROC [bs: BiScroller, direction: Vec] 
RETURNS [min, max: Vec] =
BEGIN
vl: VecList ← bs.style.ViewportOf[bs];
e: Geom2D.ExtremaRec ←
Geom2D.Extreme[direction, vl.first,
Geom2D.Extreme[direction, vl.rest.first,
Geom2D.Extreme[direction, vl.rest.rest.first,
Geom2D.StartExtreme[direction, vl.rest.rest.rest.first]]]];
RETURN [e.minV, e.maxV];
END;
 
ViewportBox: 
PUBLIC 
PROC [bs: BiScroller] 
RETURNS [bb: Rect] = {
Pt: 
PROC [v: Vec] 
RETURNS [a: Rect] = 
INLINE
{a ← [v.x, v.y, 0, 0]};
 
vl: VecList ← bs.style.ViewportOf[bs];
bb ← Geom2D.UpdateRects[ Geom2D.UpdateRects[ Geom2D.UpdateRects[
Pt[vl.first],
Pt[vl.rest.first]],
Pt[vl.rest.rest.first]],
Pt[vl.rest.rest.rest.first]];
};
 
GenID: 
PUBLIC 
PROC [BiScroller] 
RETURNS [Transform] 
--TransformGenerator-- =
{RETURN [Geom2D.id]};
 
ConstantVector: 
PROC [bs: BiScroller] 
RETURNS [cv: Vec] = {
v: Viewer ← bs.style.QuaViewer[bs, TRUE];
cv ← [v.cw/2, v.ch/2];
};
 
ViewLimitsOfImage: 
PUBLIC 
PROC [bs: BiScroller, axis: Axis] 
RETURNS [vmin, vmax: 
REAL] =
BEGIN
t: Transform ← bs.style.GetTransforms[bs].clientToViewer;
tn: Geom2D.Trans ← Geom2D.ToTrans[t];
norm, min, max: Vec;
SELECT axis 
FROM
X => norm ← [tn.dxdx, tn.dxdy];
Y => norm ← [tn.dydx, tn.dydy];
ENDCASE => ERROR;
 
[min, max] ← bs.class.common.extrema[bs.style.ClientDataOf[bs], norm];
min ← t.Transform[min];
max ← t.Transform[max];
SELECT axis 
FROM
X => {vmin ← min.x; vmax ← max.x};
Y => {vmin ← min.y; vmax ← max.y};
ENDCASE => ERROR;
 
END;
 
Scale: 
PUBLIC 
ENTRY 
PROC [bs: BiScroller, op: ScaleOp, paint: 
BOOL ← 
TRUE] =
{ENABLE ABORTED => {}; IntScale[bs, op, paint]};
 
IntScale: 
INTERNAL 
PROC [bs: BiScroller, op: ScaleOp, paint: 
BOOL ← 
TRUE] = {
cv: Vec ← ConstantVector[bs];
old: Transform ← bs.style.GetTransforms[bs].clientToViewer;
new: Transform ← old.PostTranslate[cv.Neg[]];
WITH op 
SELECT 
FROM
reset => {
v: Geom2D.Trans ← Geom2D.ToTrans[bs.class.common.vanilla[bs]];
vd: REAL ← v.dxdx*v.dydy - v.dydx*v.dxdy;
od: REAL ← old.a*old.e - old.d*old.b;
new ← new.PostScale[
RealFns.SqRt[ABS[vd/ZeroProtect[od]]]*SGN[vd]*SGN[od]
];
};
byArg => new ← new.PostScale[arg];
ENDCASE => ERROR;
 
new ← new.PostTranslate[cv];
bs.style.ChangeTransform[bs, new, paint];
};
 
SGN: 
PROC [r: 
REAL] 
RETURNS [sgn: [-1 .. 1]] = {
sgn ← 
SELECT r 
FROM
<0 => -1,
=0 => 0,
>0 => 1,
ENDCASE => ERROR};
 
ZeroProtect: 
PROC [r: 
REAL] 
RETURNS [r0: 
REAL] =
{r0 ← IF r = 0.0 THEN 1.0 ELSE r};
 
Rotate: 
PUBLIC 
ENTRY 
PROC [bs: BiScroller, op: RotateOp, paint: 
BOOL ← 
TRUE] =
{ENABLE ABORTED => {}; IntRotate[bs, op, paint]};
 
IntRotate: 
INTERNAL 
PROC [bs: BiScroller, op: RotateOp, paint: 
BOOL ← 
TRUE] = {
cv: Vec ← ConstantVector[bs];
old: Transform ← bs.style.GetTransforms[bs].clientToViewer;
new: Transform ← old.PostTranslate[cv.Neg[]];
WITH op 
SELECT 
FROM
reset => {
v: Geom2D.Trans ← Geom2D.ToTrans[bs.class.common.vanilla[bs]];
new ← new.PostRotate[
RealFns.ArcTanDeg[y: v.dydx, x: v.dxdx] -
RealFns.ArcTanDeg[y: old.d, x: old.a]
];
};
byArg => new ← new.PostRotate[arg];
ENDCASE => ERROR;
 
new ← new.PostTranslate[cv];
bs.style.ChangeTransform[bs, new, paint];
};
 
Shift: 
PUBLIC 
ENTRY 
PROC [bs: BiScroller, dx, dy: 
REAL, paint: 
BOOL ← 
TRUE] =
{ENABLE ABORTED => {}; IntShift[bs, dx, dy, paint]};
 
IntShift: 
INTERNAL 
PROC [bs: BiScroller, dx, dy: 
REAL, paint: 
BOOL ← 
TRUE] = {
old: Transform ← bs.style.GetTransforms[bs].clientToViewer;
new: Transform ← old.PostTranslate[[dx, dy]];
bs.style.ChangeTransform[bs, new, paint];
};
 
Align: 
PUBLIC 
ENTRY 
PROC [bs: BiScroller, client, viewer: Location, doX, doY, paint: 
BOOL ← 
TRUE] =
{ENABLE ABORTED => {}; IntAlign[bs, client, viewer, doX, doY, paint]};
 
IntAlign: 
INTERNAL 
PROC [bs: BiScroller, client, viewer: Location, doX, doY, paint: 
BOOL ← 
TRUE] = {
old: Transform ← bs.style.GetTransforms[bs].clientToViewer;
new: Transform;
from, to: Vec;
Blend: 
PROC [
a: 
REAL, b0, b1: 
REAL] 
RETURNS [c: 
REAL] =
{c ← (1-a)*b0 + a*b1};
 
WITH client 
SELECT 
FROM
coord => from ← old.Transform[[x, y]];
fraction => {
min, max: REAL;
IF doX 
THEN {
[min, max] ← ViewLimitsOfImage[bs, X];
from.x ← Blend[fx, min, max];
};
 
IF doY 
THEN {
[min, max] ← ViewLimitsOfImage[bs, Y];
from.y ← Blend[fy, min, max];
};
 
};
ENDCASE => ERROR;
 
WITH viewer 
SELECT 
FROM
coord => to ← [x, y];
fraction => {
v: Viewer ← bs.style.QuaViewer[bs, TRUE];
to ← [fx*v.cw, fy*v.ch];
};
ENDCASE => ERROR;
 
IF NOT doX THEN to.x ← from.x;
IF NOT doY THEN to.y ← from.y;
new ← old.PostTranslate[from.Neg[]].PostTranslate[to];
bs.style.ChangeTransform[bs, new, paint];
};
 
BoxScale: 
PUBLIC 
ENTRY 
PROC [bs: BiScroller, from, to: Rect 
--both in viewer coords--, paint: 
BOOL ← 
TRUE] =
{ENABLE ABORTED => {}; IntBoxScale[bs, from, to, paint]};
 
IntBoxScale: 
INTERNAL 
PROC [bs: BiScroller, from, to: Rect 
--both in viewer coords--, paint: 
BOOL ← 
TRUE] = {
cv: Vec ← ConstantVector[bs];
old: Transform ← bs.style.GetTransforms[bs].clientToViewer;
new: Transform ← old.PostTranslate[[
-(from.x + from.w/2),
-(from.y + from.h/2)]];
ndx, ndy, odx, ody, sx, sy: REAL;
ndx ← to.w;
ndy ← to.h;
odx ← from.w;
ody ← from.h;
sx ← IF ndx=0 OR odx=0 THEN 1 ELSE ndx/odx;
sy ← IF ndy=0 OR ody=0 THEN 1 ELSE ndy/ody;
IF NOT bs.class.common.mayStretch THEN sx ← sy ← MIN[sx, sy];
IF bs.class.common.preferIntegerCoefficients
THEN {sx ← Round[sx]; sy ← Round[sy]};
 
new ← new.PostScale2[[sx, sy]];
new ← new.PostTranslate[[
(to.x + to.w/2),
(to.y + to.h/2)]];
bs.style.ChangeTransform[bs, new, paint];
};
 
Round: 
PROC [r: 
REAL] 
RETURNS [rr: 
REAL] = {
i: INT ← Real.RoundLI[r];
rr ← IF (i=0) # (r=0) THEN r ELSE REAL[i];
};
 
GetArg: 
PROC 
RETURNS [valid: 
BOOL, arg: 
REAL] = {
sel: ROPE ← ViewerTools.GetSelectionContents[];
s: IO.STREAM;
valid ← 
SELECT sel.Length[] 
FROM
> 1 => TRUE,
> 0 => sel.Fetch[0] IN ['0 .. '9],
ENDCASE => FALSE;
IF NOT valid THEN RETURN;
s ← IO.RIS[sel];
arg ← s.GetReal[!
IO.Error, 
IO.EndOfStream => {
valid ← FALSE;
MessageWindow.Append[
message: IO.PutFR["Select a number, not %g", IO.refAny[sel]],
clearFirst: TRUE];
MessageWindow.Blink[];
CONTINUE}
 
];
s.Close[];
};
 
GetBS: 
PROC [v: Viewer] 
RETURNS [bs: BiScroller] = {
FOR v ← v, v.parent WHILE (bs ← NARROW[ViewerOps.FetchProp[v, $SubBiScroller]]) = NIL DO NULL ENDLOOP;
};
 
SetBS: 
PUBLIC 
PROC [v: Viewer, bs: BiScroller] = {
FOR v ← v, v.parent 
WHILE v # 
NIL 
DO
ViewerOps.AddProp[v, $SubBiScroller, bs]
ENDLOOP;
 
};
 
ScaleButt: 
ENTRY 
PROC [parent: 
REF 
ANY, clientData: 
REF 
ANY ← 
NIL, mouseButton: Menus.MouseButton ← red, shift, control: 
BOOL ← 
FALSE] 
--Menus.MenuProc-- = {
ENABLE 
ABORTED => 
NULL;
v: Viewer ← NARROW[parent];
bs: BiScroller ← GetBS[v];
valid: BOOL;
arg: REAL;
[valid, arg] ← GetArg[];
IF NOT valid THEN arg ← 2.0;
IntScale[
bs,
SELECT mouseButton 
FROM
red => [byArg[arg]],
yellow => [reset[]],
blue => [byArg[1.0/arg]],
ENDCASE => ERROR
 
];
};
 
RotateButt: 
ENTRY 
PROC [parent: 
REF 
ANY, clientData: 
REF 
ANY ← 
NIL, mouseButton: Menus.MouseButton ← red, shift, control: 
BOOL ← 
FALSE] 
--Menus.MenuProc-- = {
ENABLE 
ABORTED => 
NULL;
v: Viewer ← NARROW[parent];
bs: BiScroller ← GetBS[v];
valid: BOOL;
arg: REAL;
[valid, arg] ← GetArg[];
IF NOT valid THEN arg ← 90;
IntRotate[
bs,
SELECT mouseButton 
FROM
red => [byArg[arg]],
yellow => [reset[]],
blue => [byArg[-arg]],
ENDCASE => ERROR
 
];
};
 
FitButt: 
ENTRY 
PROC [parent: 
REF 
ANY, clientData: 
REF 
ANY ← 
NIL, mouseButton: Menus.MouseButton ← red, shift, control: 
BOOL ← 
FALSE] 
--Menus.MenuProc-- = {
ENABLE 
ABORTED => 
NULL;
v: Viewer ← NARROW[parent];
bs: BiScroller ← GetBS[v];
iv: Viewer ← bs.style.QuaViewer[bs, TRUE];
limits: Imager.Box;
[limits.xmin, limits.xmax] ← ViewLimitsOfImage[bs, X];
[limits.ymin, limits.ymax] ← ViewLimitsOfImage[bs, Y];
IntBoxScale[bs, ImagerBox.RectFromBox[limits], [0, 0, iv.cw, iv.ch]];
};
 
ResetAndCenterButt: 
ENTRY 
PROC [parent: 
REF 
ANY, clientData: 
REF 
ANY ← 
NIL, mouseButton: Menus.MouseButton ← red, shift, control: 
BOOL ← 
FALSE] 
--Menus.MenuProc-- = {
ENABLE 
ABORTED => 
NULL;
v: Viewer ← NARROW[parent];
bs: BiScroller ← GetBS[v];
bs.style.ChangeTransform[bs, bs.class.common.vanilla[bs], FALSE];
IntAlign[bs, [fraction[0.5, 0.5]], [fraction[0.5, 0.5]]];
};
 
CenterButt: 
ENTRY 
PROC [parent: 
REF 
ANY, clientData: 
REF 
ANY ← 
NIL, mouseButton: Menus.MouseButton ← red, shift, control: 
BOOL ← 
FALSE] 
--Menus.MenuProc-- = {
ENABLE 
ABORTED => 
NULL;
v: Viewer ← NARROW[parent];
bs: BiScroller ← GetBS[v];
IntAlign[bs, [fraction[0.5, 0.5]], [fraction[0.5, 0.5]]];
};
 
VanillaButt: 
ENTRY 
PROC [parent: 
REF 
ANY, clientData: 
REF 
ANY ← 
NIL, mouseButton: Menus.MouseButton ← red, shift, control: 
BOOL ← 
FALSE] 
--Menus.MenuProc-- = {
ENABLE 
ABORTED => 
NULL;
v: Viewer ← NARROW[parent];
bs: BiScroller ← GetBS[v];
bs.style.ChangeTransform[bs, bs.class.common.vanilla[bs], TRUE];
};
 
LeftButt: 
ENTRY 
PROC [parent: 
REF 
ANY, clientData: 
REF 
ANY ← 
NIL, mouseButton: Menus.MouseButton ← red, shift, control: 
BOOL ← 
FALSE] 
--Menus.MenuProc-- = {
ENABLE 
ABORTED => 
NULL;
v: Viewer ← NARROW[parent];
bs: BiScroller ← GetBS[v];
IntAlign[bs: bs, client: [fraction[0.0, 0.0]], viewer: [fraction[0.0, 0.0]], doY: FALSE];
};
 
RightButt: 
ENTRY 
PROC [parent: 
REF 
ANY, clientData: 
REF 
ANY ← 
NIL, mouseButton: Menus.MouseButton ← red, shift, control: 
BOOL ← 
FALSE] 
--Menus.MenuProc-- = {
ENABLE 
ABORTED => 
NULL;
v: Viewer ← NARROW[parent];
bs: BiScroller ← GetBS[v];
IntAlign[bs: bs, client: [fraction[1.0, 0.0]], viewer: [fraction[1.0, 0.0]], doY: FALSE];
};
 
BottomButt: 
ENTRY 
PROC [parent: 
REF 
ANY, clientData: 
REF 
ANY ← 
NIL, mouseButton: Menus.MouseButton ← red, shift, control: 
BOOL ← 
FALSE] 
--Menus.MenuProc-- = {
ENABLE 
ABORTED => 
NULL;
v: Viewer ← NARROW[parent];
bs: BiScroller ← GetBS[v];
IntAlign[bs: bs, client: [fraction[0.0, 0.0]], viewer: [fraction[0.0, 0.0]], doX: FALSE];
};
 
TopButt: 
ENTRY 
PROC [parent: 
REF 
ANY, clientData: 
REF 
ANY ← 
NIL, mouseButton: Menus.MouseButton ← red, shift, control: 
BOOL ← 
FALSE] 
--Menus.MenuProc-- = {
ENABLE 
ABORTED => 
NULL;
v: Viewer ← NARROW[parent];
bs: BiScroller ← GetBS[v];
IntAlign[bs: bs, client: [fraction[0.0, 1.0]], viewer: [fraction[0.0, 1.0]], doX: FALSE];
};
 
CatenateMenus: 
PUBLIC 
PROC [pre, post: Menus.Menu] 
RETURNS [menu: Menus.Menu] = {
preLines, postLines: INT;
menu ← IF pre = NIL THEN Menus.CreateMenu[0] ELSE pre.CopyMenu[];
preLines ← menu.GetNumberOfLines[];
postLines ← post.GetNumberOfLines[];
menu.ChangeNumberOfLines[preLines + postLines];
FOR i: INT IN [0 .. postLines) DO menu.SetLine[preLines+i, post.GetLine[i]] ENDLOOP;
};
 
SetViewerPosition: 
PUBLIC 
PROC [v: Viewer, x, y, w, h: 
INTEGER] = {
ViewerOps.MoveViewer[v, x, y, w, h, FALSE];
vbs: INTEGER = IF v.border THEN ViewerSpecs.windowBorderSize ELSE 0;
v.wx ← x; v.wy ← y; v.ww ← w; v.wh ← h;
v.ch ← v.wh - (vbs+vbs) - (IF v.column=static OR v.parent#NIL THEN 0 ELSE ViewerSpecs.captionHeight);
IF v.menu#NIL THEN v.ch ← v.ch - (v.menu.linesUsed*ViewerSpecs.menuHeight);
v.cx ← v.wx + vbs + (IF v.scrollable THEN ViewerSpecs.scrollBarW ELSE 0);
v.cy ← v.wy + vbs;
v.cw ← v.ww - (v.cx-v.wx) - vbs;
};
 
Start: 
PROC = {
bsMenu.AppendMenuEntry[Menus.CreateEntry["Scale", ScaleButt], 0];
bsMenu.AppendMenuEntry[Menus.CreateEntry["Rotate", RotateButt], 0];
bsMenu.AppendMenuEntry[Menus.CreateEntry["Fit", FitButt], 0];
bsMenu.AppendMenuEntry[Menus.CreateEntry["ResetAndCenter", ResetAndCenterButt], 0];
bsMenu.AppendMenuEntry[Menus.CreateEntry["Center", CenterButt], 0];
bsMenu.AppendMenuEntry[Menus.CreateEntry["Vanilla", VanillaButt], 0];
bsMenu.AppendMenuEntry[Menus.CreateEntry["Left", LeftButt], 0];
bsMenu.AppendMenuEntry[Menus.CreateEntry["Right", RightButt], 0];
bsMenu.AppendMenuEntry[Menus.CreateEntry["Top", TopButt], 0];
bsMenu.AppendMenuEntry[Menus.CreateEntry["Bottom", BottomButt], 0];
};
 
Start[];
END.