WhiteboardViewersImpl.mesa
Copyright (C) 1984 by Xerox Corporation. All rights reserved.
last edited by:
John Maxwell on: September 22, 1982 12:19 pm
Willie-Sue on: February 22, 1983 4:19 pm
Cattell on: April 21, 1983 4:02 pm
Donahue, May 22, 1985 9:16:26 am PDT
Widom, August 24, 1984 7:21:39 pm PDT
Last Edited by: Winkler, December 18, 1984 11:00:10 am PST
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;
WhiteboardViewersImpl: CEDAR PROGRAM
IMPORTS
ClassIncreek, DBIcons, Icons, Imager, ImagerBackdoor, ImagerManhattan, ImagerPath, ImagerPixelMap, InputFocus, InterminalBackdoor, IO, MBQueue, Menus, MessageWindow, Process, Rope, Terminal, TIPUser, ViewerEvents, ViewerLocks, ViewerOps, ViewerPrivate, ViewerTools, WhiteboardDB, WhiteboardOps
EXPORTS WhiteboardViewers
SHARES ViewerOps =
BEGIN
OPEN ViewerClasses;
ROPE: TYPE = Rope.ROPE;
ViewerPair: TYPE = RECORD[from, to: Viewer];
LineList: TYPE = LIST OF ViewerPair;
Painting procedures
h: INTEGER ← 620;
w: INTEGER ← 30;
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: BOOLTRUE] =
{ 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: INTEGERLAST[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: BOOLEANTRUE] = {
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;
Event Registration
DoDeregister: ViewerOps.EnumProc = {
registration: ViewerEvents.EventRegistration = NARROW[ViewerOps.FetchProp[v, $registration]];
IF registration # NIL THEN ViewerEvents.UnRegisterEventProc[registration, edit] };
DeRegister: ViewerEvents.EventProc =
TRUSTED { Process.Detach[ FORK ViewerOps.EnumerateChildren[viewer, DoDeregister] ] };
Initialization
CreateWhiteboardClass[]; CreateIconClass[]; CreateMenu[];
[] ← ViewerEvents.RegisterEventProc[proc: DeRegister, event: destroy, filter: $Whiteboard];
END...