DIRECTORY
ClassIncreek,
DB,
DBIcons USING [GetIcon],
Icons USING [DrawIcon, IconFlavor, iconW, iconH],
Imager,
ImagerBackdoor,
ImagerManhattan USING [Difference, Polygon],
ImagerPath USING [ Trajectory, ArcTo, MoveTo ],
ImagerPixelMap USING [Clip, Create, DeviceRectangle, Intersect, Fill, PixelMap, Transfer, CreateFrameBuffer],
InputFocus,
Interminal,
InterminalBackdoor,
IO,
MBQueue,
Menus,
MessageWindow,
Process USING [Detach],
Rope USING [Concat, Flatten, Find, Length, ROPE],
Terminal,
TIPUser,
ViewerClasses USING [PaintProc, Viewer, ViewerClass, ViewerClassRec, ViewerRec, ViewerFlavor],
ViewerEvents,
ViewerLocks USING [CallUnderWriteLock],
ViewerOps,
ViewerPrivate,
ViewerTools USING [MakeNewTextViewer, TiogaContents, SetTiogaContents],
WhiteboardDB,
WhiteboardViewers,
WhiteboardOps;
Painting procedures
h: INTEGER ← 620;
wb:
PUBLIC ViewerClasses.ViewerFlavor ← $Whiteboard;
the ViewerFlavor of whiteboards
whiteboardClass: ViewerClasses.ViewerClass;
icon:
PUBLIC ViewerClasses.ViewerFlavor ← $WhiteboardIcon;
the ViewerFlavor of icons appearing on whiteboards
iconClass: ViewerClasses.ViewerClass;
text:
PUBLIC ViewerClasses.ViewerFlavor ← $Text;
the ViewerFlavor of text boxes appearing on whiteboards
CreateWhiteboardClass:
PROCEDURE =
BEGIN
tipTableName: ROPE = "Whiteboard.tip";
tipTable: TIPUser.TIPTable = TIPUser.InstantiateNewTIPTable[tipTableName];
whiteboardClass ← NEW[ViewerClasses.ViewerClassRec ← []];
whiteboardClass^ ← ViewerOps.FetchViewerClass[$Container]^;
whiteboardClass.flavor ← wb;
whiteboardClass.notify ← WhiteboardOps.Notify;
whiteboardClass.modify ← NIL;
whiteboardClass.tipTable ← tipTable;
whiteboardClass.cursor ← crossHairsCircle;
whiteboardClass.topDownCoordSys ← TRUE;
PaintContainer ← whiteboardClass.paint;
whiteboardClass.paint ← PaintWhiteboard;
whiteboardClass.icon ← private;
ViewerOps.RegisterViewerClass[wb, whiteboardClass];
END;
CreateIconClass:
PROCEDURE =
BEGIN
iconClass ← NEW[ViewerClasses.ViewerClassRec ← []];
iconClass^ ← ViewerOps.FetchViewerClass[wb]^;
iconClass.flavor ← icon;
iconClass.paint ← PaintIcon;
iconClass.init ← NIL;
ViewerOps.RegisterViewerClass[icon, iconClass];
END;
PaintContainer: PaintProc;
PaintWhiteboard: ViewerClasses.PaintProc =
BEGIN
IF self.iconic THEN { [] ← PaintIconic[self, context, whatChanged, TRUE]; RETURN };
[] ← PaintContainer[self, context, whatChanged, clear];
[] ← PaintLines[self, context]
END;
PaintLines:
PROC[self: Viewer, context: Imager.Context] =
BEGIN
x1, y1, x2, y2: INTEGER;
PaintTheLine:
PROC[context: Imager.Context] = {
AddLine[ctx: context, fromX: x1, fromY: y1, toX: x2, toY: y2] };
lines: LineList ← NARROW[ViewerOps.FetchProp[self, $Lines], LineList];
FOR ll: LineList ← lines, ll.rest
UNTIL ll =
NIL
DO
IF
NOT ll.first.from.destroyed
AND
NOT ll.first.to.destroyed
THEN {
x1 ← ll.first.from.wx + (ll.first.from.ww/2);
y1 ← self.ch - (ll.first.from.wy + ll.first.from.wh);
x2 ← ll.first.to.wx + (ll.first.to.ww/2);
y2 ← self.ch - ll.first.to.wy;
ViewerPrivate.PaintWindow[self, PaintTheLine] }
ENDLOOP;
END;
PaintIconic:
PUBLIC ViewerClasses.PaintProc =
BEGIN
DrawIcon:
PROC ~ {
IF self.newVersion
THEN {
IF DirtyWBicon = unInit THEN ERROR WhiteboardDB.WBError[$ServerDown];
Icons.DrawIcon[DirtyWBicon, context, 0, 0, Strip[self.name]] }
ELSE {
IF WBicon = unInit THEN ERROR WhiteboardDB.WBError[$ServerDown];
Icons.DrawIcon[WBicon, context, 0, 0, Strip[self.name]] };
};
PaintInverted:
PROC ~ {
Imager.SetColor[context, ImagerBackdoor.invert];
Imager.MaskRectangleI[context, 0, 0, Icons.iconW, Icons.iconH] };
IF ~self.newVersion
THEN
FOR child: Viewer ← self.child, child.sibling
WHILE child #
NIL
DO
IF child.newVersion THEN {self.newVersion ← TRUE; EXIT};
ENDLOOP;
IF DirtyWBicon = unInit THEN DirtyWBicon ← DBIcons.GetIcon["DirtyWhiteboard"];
IF WBicon = unInit THEN WBicon ← DBIcons.GetIcon["Whiteboard"];
Imager.DoSave[context, DrawIcon];
IF ViewerPrivate.selectedIcon = self THEN Imager.DoSave[context, PaintInverted]
END;
Strip:
PROCEDURE[name:
ROPE]
RETURNS[
ROPE] =
INLINE
BEGIN
pos: INT;
IF name = NIL THEN RETURN[NIL];
IF (pos ← name.Find[":"]) > 0
THEN
RETURN[name.Flatten[pos+2, name.Length[]]]
ELSE RETURN[name];
END;
PaintIcon:
PUBLIC ViewerClasses.PaintProc =
BEGIN
name: ROPE = self.name;
iconRef: REF Icons.IconFlavor = NARROW[ViewerOps.FetchProp[self, $IconFlavor]];
icon: Icons.IconFlavor = IF iconRef = NIL THEN unInit ELSE iconRef^;
DrawIcon: PROC ~ { Icons.DrawIcon[icon, context, 0, 0, name] };
Imager.DoSave[context, DrawIcon];
IF self.spare0
THEN {
this icon has been inverted; keep it that way
PaintInverted:
PROC ~ {
Imager.SetColor[context, ImagerBackdoor.invert];
Imager.MaskRectangleI[context, 0, 0, Icons.iconW, Icons.iconH] };
Imager.DoSave[context, PaintInverted] }
END;
InvertIcon:
PUBLIC
PROC[icon: Viewer, paint:
BOOL ←
TRUE] =
{ icon.spare0 ← NOT icon.spare0; IF paint THEN ViewerOps.PaintViewer[icon, all] };
WBicon: Icons.IconFlavor ← DBIcons.GetIcon["Whiteboard"];
DirtyWBicon: Icons.IconFlavor ← DBIcons.GetIcon["DirtyWhiteboard"];
Child addition procedures
(the AddTextBox and AddIcon procedures DO NOT paint the viewers they create)
AddTextBox:
PUBLIC
PROC[wb: Viewer, x, y, w, h:
INTEGER, contents: ViewerTools.TiogaContents]
RETURNS[child: Viewer] =
BEGIN
grid: NAT = GetGrid[wb];
MakeChild:
PROC[] = {
child ← ViewerTools.MakeNewTextViewer[paint: FALSE, info: [parent: wb, wx: 0, wy: 0, ww: 800, wh: 800]];
IF contents #
NIL
THEN
ViewerTools.SetTiogaContents[viewer: child, contents: contents, paint: FALSE];
ViewerOps.MoveViewer[child, x-(x MOD grid), y-(y MOD grid), w-(w MOD grid), h-(h MOD grid), FALSE] };
registration: ViewerEvents.EventRegistration;
ViewerLocks.CallUnderWriteLock[MakeChild, wb];
wb.newVersion ← TRUE;
registration ← ViewerEvents.RegisterEventProc[SetNew, edit, child];
ViewerOps.AddProp[child, $registration, registration]
END;
SetNew: ViewerEvents.EventProc =
{ viewer.parent.newVersion ←
TRUE;
WhiteboardDB.EditText[viewer];
TRUSTED
{ Process.Detach[FORK ViewerOps.PaintViewer[viewer: viewer.parent, hint: caption]] } };
AddIcon:
PUBLIC
PROC[wb: Viewer, name:
ROPE, icon: Icons.IconFlavor, x, y:
INTEGER]
RETURNS[child: Viewer] =
BEGIN
OPEN ViewerOps;
grid: NAT = GetGrid[wb];
add the icon
IF name = NIL THEN RETURN; -- nothing we can do here
child ← CreateViewer[flavor: $WhiteboardIcon, paint:
FALSE,
info: [
parent: wb,
wx: x - (x MOD grid), wy: y - (y MOD grid),
wh: 64, ww: 64,
name: name,
border: FALSE,
scrollable: FALSE]];
ViewerOps.AddProp[child, $IconFlavor, NEW[Icons.IconFlavor ← icon]];
wb.newVersion ← TRUE;
END;
GetGrid:
PUBLIC
PROC[ v: Viewer ]
RETURNS[ grid:
INT ] = {
gridSize: REF INT = NARROW[ViewerOps.FetchProp[v, $gridSize]];
RETURN[IF gridSize = NIL THEN 1 ELSE gridSize^] };
SetGrid:
PUBLIC
PROC[v: Viewer, grid:
INT] = {
gridSize: REF INT = NARROW[ViewerOps.FetchProp[v, $gridSize]];
newName: IO.STREAM = IO.ROS[];
oldEntry: Menus.MenuEntry ← NARROW[ViewerOps.FetchProp[v, $gridEntry]];
newEntry: Menus.MenuEntry;
IF oldEntry = NIL THEN oldEntry ← Menus.FindEntry[v.menu, "Grid: 1"];
IO.PutF[newName, "Grid: %2g", IO.int[grid]];
IF gridSize = NIL THEN ViewerOps.AddProp[v, $gridSize, NEW[INT ← grid]]
ELSE gridSize^ ← grid;
newEntry ← MBQueue.CreateMenuEntry[ActionQueue, IO.RopeFromROS[newName], SetGridProc];
Menus.ReplaceMenuEntry[v.menu, oldEntry, newEntry];
ViewerOps.AddProp[v, $gridEntry, newEntry];
v.newVersion ← TRUE;
ViewerOps.PaintViewer[v, all];
newName.Close[] };
ResetGrid:
PUBLIC
PROC[v: Viewer, grid:
INT] = {
gridSize: REF INT = NARROW[ViewerOps.FetchProp[v, $gridSize]];
IF gridSize = NIL THEN ViewerOps.AddProp[v, $gridSize, NEW[INT ← grid]]
ELSE gridSize^ ← grid;
IF grid = 1
THEN {
ViewerOps.PaintViewer[v, all];
ViewerOps.AddProp[v, $gridEntry, NIL];
RETURN};
BEGIN
newName: IO.STREAM = IO.ROS[];
oldEntry: Menus.MenuEntry ← NARROW[ViewerOps.FetchProp[v, $gridEntry]];
newEntry: Menus.MenuEntry;
IF oldEntry = NIL THEN oldEntry ← Menus.FindEntry[v.menu, "Grid: 1"];
IO.PutF[newName, "Grid: %2g", IO.int[grid]];
newEntry ← MBQueue.CreateMenuEntry[ActionQueue, IO.RopeFromROS[newName], SetGridProc];
Menus.ReplaceMenuEntry[v.menu, oldEntry, newEntry];
ViewerOps.AddProp[v, $gridEntry, newEntry];
ViewerOps.PaintViewer[v, all];
newName.Close[]
END };
The Menu and Operations on Whiteboards
whiteboardMenu: PUBLIC Menus.Menu = Menus.CreateMenu[];
ActionQueue: MBQueue.Queue = MBQueue.Create[];
SetMenu:
PUBLIC
PROC[v: Viewer] = {
ViewerOps.SetMenu[v, whiteboardMenu];
ViewerOps.AddProp[v, $gridEntry, NIL]
};
CreateMenu:
PROCEDURE =
BEGIN
Build the first line of the menu
Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "Save", SaveProc]];
Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "Grid: 1", SetGridProc]];
Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "HELP", Instructions]];
Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "Erase", Erase]];
Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "AddCommandFile", AddCommandFile]];
Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "AddSelected", AddSelected]];
Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[q: ActionQueue, name: "Reset", proc: ResetProc, guarded: TRUE]];
Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "Freeze", Freeze]];
END;
SaveProc: Menus.MenuProc = {
ENABLE WhiteboardDB.WBError => {
MessageWindow.Clear[];
SELECT reason FROM
$ServerDown => {
MessageWindow.Append["Didn't Save -- server unavailable; retry later"]; CONTINUE };
$TransactionAbort => {
MessageWindow.Append["Didn't Save -- transaction aborted; retry later"]; CONTINUE };
$ReadOnly =>{
MessageWindow.Append["Can't Save -- database is readonly"]; CONTINUE };
$WrongVersion =>{
MessageWindow.Append["Can't Save -- whiteboard has wrong version"]; CONTINUE };
ENDCASE => REJECT
};
v: Viewer = NARROW[parent];
KillInputFocus[v];
WhiteboardDB.Save[v] };
KillInputFocus:
PROC [v: Viewer] =
BEGIN
focus: Viewer ← InputFocus.GetInputFocus[].owner;
WHILE focus # v
AND focus #
NIL
DO
focus ← focus.parent
ENDLOOP;
IF focus = v THEN InputFocus.SetInputFocus[];
END;
SetGridProc: Menus.MenuProc = {
v: Viewer = NARROW[parent];
oldGrid: NAT = GetGrid[v];
newGrid: NAT = IF mouseButton = red THEN MIN[oldGrid*2, 32] ELSE MAX[oldGrid/2, 1];
KillInputFocus[v];
SetGrid[v, newGrid] };
ResetProc: Menus.MenuProc =
{ v: Viewer =
NARROW[parent];
KillInputFocus[v];
WhiteboardDB.Reset[v] };
AddSelected: Menus.MenuProc =
{viewer: Viewer = NARROW[parent]; WhiteboardOps.Notify[viewer, LIST[$AddSelected]] };
AddCommandFile: Menus.MenuProc =
{viewer: Viewer = NARROW[parent]; WhiteboardOps.Notify[viewer, LIST[$AddCommandFile]] };
Instructions: Menus.MenuProc =
{viewer: Viewer = NARROW[parent]; WhiteboardOps.Notify[viewer, LIST[$Instructions]] };
Erase: Menus.MenuProc =
{viewer: Viewer = NARROW[parent]; WhiteboardOps.Notify[viewer, LIST[$Erase]] };
Freeze: Menus.MenuProc =
{viewer: Viewer = NARROW[parent]; WhiteboardOps.Notify[viewer, LIST[$Freeze]] };
Child manipulation procedures
moveGrain: INTEGER ← 1;
MoveChild:
PUBLIC
PROCEDURE[child: Viewer] =
TRUSTED BEGIN
parent: Viewer;
dx, dy: INTEGER;
grid: NAT;
terminal: Terminal.Virtual ~ InterminalBackdoor.terminal;
frame: Terminal.FrameBuffer ~ Terminal.GetBWFrameBuffer[terminal]; -- b/w display
creek: ClassIncreek.Increek ← ClassIncreek.NewStdIncreek[];
position: ClassIncreek.ViewPosition ← creek.GetPositionFrom[];
oldPosition: Interminal.MousePosition ← position.mousePosition;
actions: INTEGER ← 0;
saveMap: ImagerPixelMap.PixelMap;
restoreMap: ImagerPixelMap.PixelMap;
oldBox: ImagerManhattan.Polygon;
newBox: ImagerManhattan.Polygon;
parentBox: ImagerPixelMap.DeviceRectangle;
toRestore, toSave: ImagerManhattan.Polygon;
screenX, screenY: INTEGER;
IF child # NIL THEN parent ← child.parent ELSE RETURN;
saveMap ← ImagerPixelMap.Create[0, [0, 0, frame.height, frame.width]];
TRUSTED { restoreMap ← ImagerPixelMap.CreateFrameBuffer[
pointer: frame.base, words: frame.vm.words, lgBitsPerPixel: 0,
rast: frame.wordsPerLine, lines: frame.height, ref: frame] };
[screenX, screenY] ← ViewerOps.UserToScreenCoords[parent.parent, parent.cx, parent.cy];
screenY ← restoreMap.sSize - screenY - parent.ch;
parentBox ← [screenY, screenX, parent.ch, parent.cw];
[screenX, screenY] ← ViewerOps.UserToScreenCoords[parent, child.wx, parent.ch - child.wy];
screenY ← restoreMap.sSize - screenY;
oldBox ← LIST[[screenY, screenX, child.wh, child.ww]];
ImagerPixelMap.Fill[saveMap, oldBox.first, 0];
grid ← GetGrid[parent];
DO
a: ClassIncreek.ActionBody ← creek.GetAction[acceptance: clicksAndMotion];
WITH a
SELECT
FROM
keyUp => IF value = Red THEN EXIT;
ENDCASE;
only notice every moveGrain-th action
IF (actions
MOD moveGrain) = 0
THEN {
dx ← position^.mousePosition.mouseX - oldPosition.mouseX;
dy ← -(position^.mousePosition.mouseY - oldPosition.mouseY);
IF dx = 0 AND dy = 0 THEN LOOP;
IF child.wx + dx < 0 THEN EXIT;
IF child.wy + dy < 0 THEN EXIT;
IF child.wx + child.ww + dx > parent.cw THEN EXIT;
IF child.wy + child.wh + dy > parent.ch THEN EXIT;
screenX ← screenX + dx;
screenY ← screenY + dy;
newBox ← LIST[[screenY, screenX, child.wh, child.ww]];
toRestore ← ImagerManhattan.Difference[oldBox, newBox];
toSave ← ImagerManhattan.Difference[newBox, oldBox];
save the to-be-obscured screen bits
FOR p:
LIST
OF ImagerPixelMap.DeviceRectangle ← toSave, p.rest
UNTIL p=
NIL
DO
ImagerPixelMap.Transfer[saveMap, ImagerPixelMap.Clip[restoreMap, p.first]];
ENDLOOP;
move the entity
MoveViewer[child, child.wx + dx, child.wy + dy, child.ww, child.wh];
restore uncovered bits
FOR p:
LIST
OF ImagerPixelMap.DeviceRectangle ← toRestore, p.rest
UNTIL p=
NIL
DO
ImagerPixelMap.Transfer[ImagerPixelMap.Clip[restoreMap, ImagerPixelMap.Intersect[p.first, parentBox]], saveMap];
ENDLOOP;
oldPosition ← position^.mousePosition;
oldBox ← newBox };
actions ← actions + 1;
ENDLOOP;
child.parent.newVersion ← TRUE;
IF grid # 1
THEN
ViewerOps.MoveViewer[child, child.wx-(child.wx
MOD grid),
child.wy-(child.wy MOD grid), child.ww, child.wh, FALSE];
ViewerOps.PaintViewer[parent, all]; -- repaint everything
[] ← ClassIncreek.Release[creek];
WhiteboardDB.Move[child];
END;
boxW: INTEGER = 128;
boxH: INTEGER = 32;
corner: {ll, lr, ul, ur};
growGrain: INTEGER ← 2;
GrowBox:
PUBLIC
PROCEDURE[wb: Viewer, box: Viewer, x, y:
INTEGER] =
TRUSTED BEGIN
dx, dy: INTEGER;
grid: NAT = GetGrid[wb];
terminal: Terminal.Virtual ~ InterminalBackdoor.terminal;
frame: Terminal.FrameBuffer ~ Terminal.GetBWFrameBuffer[terminal]; -- b/w display
creek: ClassIncreek.Increek ← ClassIncreek.NewStdIncreek[];
position: ClassIncreek.ViewPosition ← creek.GetPositionFrom[];
oldPosition: Interminal.MousePosition ← position.mousePosition;
actions: INTEGER ← 0;
saveMap: ImagerPixelMap.PixelMap;
restoreMap: ImagerPixelMap.PixelMap;
oldBox: ImagerManhattan.Polygon;
newBox: ImagerManhattan.Polygon;
wbBox: ImagerPixelMap.DeviceRectangle;
toRestore, toSave: ImagerManhattan.Polygon;
screenX, screenY: INTEGER;
newX, newY, newW, newH: INTEGER;
IF box = NIL THEN RETURN;
saveMap ← ImagerPixelMap.Create[0, [0, 0, frame.height, frame.width]];
TRUSTED { restoreMap ← ImagerPixelMap.CreateFrameBuffer[
pointer: frame.base, words: frame.vm.words, lgBitsPerPixel: 0,
rast: frame.wordsPerLine, lines: frame.height, ref: frame] };
[screenX, screenY] ← ViewerOps.UserToScreenCoords[wb.parent, wb.cx, wb.cy];
screenY ← restoreMap.sSize - screenY - wb.ch;
wbBox ← [screenY, screenX, wb.ch, wb.cw];
[screenX, screenY] ← ViewerOps.UserToScreenCoords[wb, box.wx, wb.ch - box.wy];
screenY ← restoreMap.sSize - screenY;
oldBox ← LIST[[screenY, screenX, box.wh, box.ww]];
ImagerPixelMap.Fill[saveMap, oldBox.first, 0];
DO
a: ClassIncreek.ActionBody ← creek.GetAction[acceptance: clicksAndMotion];
WITH a
SELECT
FROM
keyUp => IF value = Blue THEN EXIT;
ENDCASE;
only notice every growGrain-th action
IF (actions
MOD growGrain) = 0
THEN {
dx ← position^.mousePosition.mouseX - oldPosition.mouseX;
dy ← -(position^.mousePosition.mouseY - oldPosition.mouseY);
IF dx = 0 AND dy = 0 THEN LOOP;
set limits on where it can end up
IF (corner = ll OR corner = ul) AND box.wx + dx < 0 THEN LOOP;
IF (corner = ul OR corner = ur) AND box.wy + dy < 0 THEN LOOP;
IF (corner = ur OR corner = lr) AND box.wx + box.ww + dx > wb.cw THEN EXIT;
IF (corner = ll OR corner = lr) AND box.wy + box.wh + dy > wb.ch THEN EXIT;
set limits on the minimum size
IF (corner = lr OR corner = ur) AND box.ww + dx < boxW THEN LOOP;
IF (corner = ll OR corner = lr) AND box.wh + dy < boxH THEN LOOP;
IF (corner = ll OR corner = ul) AND box.ww - dx < boxW THEN LOOP;
IF (corner = ul OR corner = ur) AND box.wh - dy < boxH THEN LOOP;
move the corner
IF dx = 0 AND dy = 0 THEN LOOP;
SELECT corner FROM
ul => {
newX ← box.wx + dx;
newY ← box.wy + dy;
newW ← box.ww - dx;
newH ← box.wh - dy;
screenX ← screenX + dx;
screenY ← screenY + dy };
ur => {
newX ← box.wx;
newY ← box.wy + dy;
newW ← box.ww + dx;
newH ← box.wh - dy;
screenY ← screenY + dy };
ll => {
newX ← box.wx + dx;
newY ← box.wy;
newW ← box.ww - dx;
newH ← box.wh + dy;
screenX ← screenX + dx };
lr => {
newX ← box.wx;
newY ← box.wy;
newW ← box.ww + dx;
newH ← box.wh + dy };
ENDCASE;
newBox ← LIST[[screenY, screenX, newH, newW]];
toRestore ← ImagerManhattan.Difference[oldBox, newBox];
toSave ← ImagerManhattan.Difference[newBox, oldBox];
save the to-be-obscured screen bits
FOR p:
LIST
OF ImagerPixelMap.DeviceRectangle ← toSave, p.rest
UNTIL p=
NIL
DO
ImagerPixelMap.Transfer[saveMap, ImagerPixelMap.Clip[restoreMap, p.first]];
ENDLOOP;
move the box
MoveViewer[box, newX, newY, newW, newH];
restore uncovered bits
FOR p:
LIST
OF ImagerPixelMap.DeviceRectangle ← toRestore, p.rest
UNTIL p=
NIL
DO
ImagerPixelMap.Transfer[ImagerPixelMap.Clip[restoreMap, ImagerPixelMap.Intersect[p.first, wbBox]], saveMap];
ENDLOOP;
oldPosition ← position^.mousePosition;
oldBox ← newBox };
actions ← actions + 1;
ENDLOOP;
wb.newVersion ← TRUE;
IF grid # 1
THEN
ViewerOps.MoveViewer[box, box.wx-(box.wx
MOD grid), box.wy-(box.wy
MOD grid),
box.ww-(box.ww MOD grid), box.wh-(box.wh MOD grid),
FALSE];
ViewerOps.PaintViewer[wb, all];
[] ← ClassIncreek.Release[creek];
WhiteboardDB.Grow[box];
END;
MoveViewer:
PROCEDURE[self: Viewer, x, y, w, h:
INTEGER] =
BEGIN
EraseViewer[self];
ViewerOps.MoveViewer[self, x, y, w, h, FALSE]; -- don't repaint
ViewerOps.PaintViewer[self, all]; -- avoids repainting parent and sibling
END;
EraseViewer:
PROCEDURE[self: Viewer] =
BEGIN
PaintItWhite:
PROC[context: Imager.Context] = {
Imager.SetColor[context, Imager.white];
Imager.MaskRectangleI[context, 0, 0, self.ww, self.wh] };
IF self = NIL THEN RETURN;
ViewerPrivate.PaintWindow[self, PaintItWhite]
END;
NearestChild:
PUBLIC
PROCEDURE[wb: Viewer, x, y:
INTEGER, type: ViewerFlavor ←
NIL]
RETURNS[nearest: Viewer] =
BEGIN
min, delta: INTEGER ← LAST[INTEGER];
IF wb.class.flavor = $WhiteboardIcon
THEN {
IF type = NIL THEN RETURN[wb];
IF type = $WhiteboardIcon THEN RETURN[wb];
RETURN[NIL]};
y ← wb.ch - y;
FOR child: Viewer ← wb.child, child.sibling
DO
IF child = NIL THEN EXIT;
IF type # NIL AND child.class.flavor # type THEN LOOP;
IF x < child.wx
THEN delta ← child.wx - x
ELSE delta ← MAX[0, x - (child.wx + child.ww)];
IF y < child.wy
THEN delta ← delta + child.wy - y
ELSE delta ← delta + MAX[0, y - (child.wy + child.wh)];
IF delta > min
THEN
LOOP;
min ← delta;
nearest ← child;
ENDLOOP;
IF min > 40 THEN {nearest ← NIL; RETURN};
if you're looking for a text box, set the corner too
min ← delta ← LAST[INTEGER];
delta ← ABS[x - nearest.wx] + ABS[y - nearest.wy];
IF delta < min THEN {min ← delta; corner ← ul};
delta ← ABS[x - (nearest.wx + nearest.ww)] + ABS[y - nearest.wy];
IF delta < min THEN {min ← delta; corner ← ur};
delta ← ABS[x - nearest.wx] + ABS[y - (nearest.wy + nearest.wh)];
IF delta < min THEN {min ← delta; corner ← ll};
delta ← ABS[x - (nearest.wx + nearest.ww)] + ABS[y - (nearest.wy + nearest.wh)];
IF delta < min THEN {min ← delta; corner ← lr};
END;
Expand:
PUBLIC
PROC[parent, wb: Viewer, wbList:
LIST
OF
ROPE] =
BEGIN
ENABLE UNWIND => NULL;
x: INTEGER = wb.wx + (wb.ww/2);
y: INTEGER = wb.wy + wb.wh;
child: Viewer;
PaintTheLine:
PROC[context: Imager.Context] = {
AddLine[context, x, parent.ch - y, child.wx + (child.ww/2), parent.ch - child.wy] };
expandX: INTEGER ← 18;
expandY: INTEGER ← y + 36;
lines: LIST OF ViewerPair;
FOR wbNames:
LIST
OF
ROPE ← wbList, wbNames.rest
UNTIL wbNames =
NIL
DO
child ← AddIcon[parent, wbNames.first, WBicon, expandX, expandY];
child.spare1 ← TRUE; -- this is how we record that it was created by Expand!!
ViewerOps.AddProp[ child, $EntityName, wbNames.first ];
ViewerOps.AddProp[ child, $IconType, $Whiteboard ];
ViewerOps.PaintViewer[viewer: child, hint: client];
ViewerPrivate.PaintWindow[parent, PaintTheLine];
lines ← NARROW[ViewerOps.FetchProp[parent, $Lines], LineList];
lines ← CONS[[wb, child], lines];
ViewerOps.AddProp[parent, $Lines, lines];
expandX ← expandX + 72;
ENDLOOP;
ViewerOps.PaintViewer[parent, caption]
END;
DontLog:
PUBLIC
PROC[icon: Viewer]
RETURNS [
BOOLEAN] =
{ RETURN[ icon = NIL OR icon.spare1 ] };
If this icon was added to the whiteboard as the result of an Expand operation, then DontLog will return TRUE -- the idea is that such expansions are not to be logged as permanent changes to the containing whiteboard (they reflect only the current state of affairs)
SetWBName:
PUBLIC
PROC[wb: Viewer, name:
ROPE, paint:
BOOLEAN ←
TRUE] = {
wb.name ← Rope.Concat["Whiteboard: ", name];
IF paint THEN ViewerOps.PaintViewer[wb, caption] };
AddLine:
PROC[ctx: Imager.Context, fromX, fromY, toX, toY:
INTEGER] =
BEGIN
littleCircle: ImagerPath.Trajectory ← ImagerPath.MoveTo[[toX+3, toY]];
littleCircle ← ImagerPath.ArcTo[littleCircle, [toX-3, toY], [toX+3, toY]];
Imager.MaskVectorI[ctx, fromX, fromY, toX, toY];
Imager.MaskFillTrajectory[ctx, littleCircle]
END;